Compare commits
No commits in common. "master" and "specialized-messages" have entirely different histories.
master
...
specialize
.github/workflows
.gitignore.gitmodulesCHANGELOG.mdREADME.mdbuild.gradle.ktscontrols-constructor
README.mdbuild.gradle.kts
src
commonMain/kotlin/space/kscience/controls/constructor
ConstructorElement.ktDeviceConstructor.ktDeviceGroup.ktDeviceState.ktModelConstructor.ktTimerState.ktboundState.kt
devices
externalState.ktflowState.ktinternalState.ktmodels
units
commonTest/kotlin/space/kscience/controls/constructor
controls-core
README.md
api
build.gradle.ktssrc
commonMain/kotlin/space/kscience/controls
api
manager
misc
peer
ports
spec
commonTest/kotlin/space/kscience/controls/api
jsMain/kotlin/space/kscience/controls/spec
jvmMain/kotlin/space/kscience/controls
jvmTest/kotlin/space/kscience/controls/ports
nativeMain/kotlin/space/kscience/controls/spec
wasmJsMain/kotlin
controls-jupyter
controls-magix
README.md
api
build.gradle.ktssrc
commonMain/kotlin/space/kscience/controls/client
commonTest/kotlin/space/kscience/controls/client
jvmTest/kotlin/space/kscience/controls/client
controls-modbus
controls-opcua
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@ -1,24 +0,0 @@
|
|||||||
name: Gradle build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ dev, master ]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
timeout-minutes: 20
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-java@v3.5.1
|
|
||||||
with:
|
|
||||||
java-version: '11'
|
|
||||||
distribution: 'liberica'
|
|
||||||
cache: 'gradle'
|
|
||||||
- name: Gradle Wrapper Validation
|
|
||||||
uses: gradle/wrapper-validation-action@v1.0.4
|
|
||||||
- name: Gradle Build
|
|
||||||
uses: gradle/gradle-build-action@v2.4.2
|
|
||||||
with:
|
|
||||||
arguments: test jvmTest
|
|
17
.github/workflows/gradle.yml
vendored
Normal file
17
.github/workflows/gradle.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: Gradle build
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew build
|
31
.github/workflows/pages.yml
vendored
31
.github/workflows/pages.yml
vendored
@ -1,31 +0,0 @@
|
|||||||
name: Dokka publication
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
|
||||||
types: [ created ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 40
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3.0.0
|
|
||||||
- uses: actions/setup-java@v3.0.0
|
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
distribution: liberica
|
|
||||||
- name: Cache konan
|
|
||||||
uses: actions/cache@v3.0.1
|
|
||||||
with:
|
|
||||||
path: ~/.konan
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gradle-
|
|
||||||
- uses: gradle/gradle-build-action@v2.4.2
|
|
||||||
with:
|
|
||||||
arguments: dokkaHtmlMultiModule --no-parallel
|
|
||||||
- uses: JamesIves/github-pages-deploy-action@v4.3.0
|
|
||||||
with:
|
|
||||||
branch: gh-pages
|
|
||||||
folder: build/dokka/htmlMultiModule
|
|
50
.github/workflows/publish.yml
vendored
50
.github/workflows/publish.yml
vendored
@ -1,50 +0,0 @@
|
|||||||
name: Gradle publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
|
||||||
types: [ created ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
environment:
|
|
||||||
name: publish
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ macOS-latest, windows-latest ]
|
|
||||||
runs-on: ${{matrix.os}}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3.0.0
|
|
||||||
- uses: actions/setup-java@v3.10.0
|
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
distribution: liberica
|
|
||||||
- name: Cache konan
|
|
||||||
uses: actions/cache@v3.0.1
|
|
||||||
with:
|
|
||||||
path: ~/.konan
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gradle-
|
|
||||||
- name: Publish Windows Artifacts
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
uses: gradle/gradle-build-action@v2.4.2
|
|
||||||
with:
|
|
||||||
arguments: |
|
|
||||||
publishAllPublicationsToSpaceRepository
|
|
||||||
-Ppublishing.targets=all
|
|
||||||
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
|
|
||||||
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}
|
|
||||||
- name: Publish Mac Artifacts
|
|
||||||
if: matrix.os == 'macOS-latest'
|
|
||||||
uses: gradle/gradle-build-action@v2.4.2
|
|
||||||
with:
|
|
||||||
arguments: |
|
|
||||||
publishMacosX64PublicationToSpaceRepository
|
|
||||||
publishMacosArm64PublicationToSpaceRepository
|
|
||||||
publishIosX64PublicationToSpaceRepository
|
|
||||||
publishIosArm64PublicationToSpaceRepository
|
|
||||||
publishIosSimulatorArm64PublicationToSpaceRepository
|
|
||||||
-Ppublishing.targets=all
|
|
||||||
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
|
|
||||||
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}
|
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,7 +1,6 @@
|
|||||||
# Created by .ignore support plugin (hsz.mobi)
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
.idea/
|
.idea/
|
||||||
.gradle
|
.gradle
|
||||||
.kotlin
|
|
||||||
|
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
*.iml
|
||||||
@ -9,7 +8,4 @@
|
|||||||
|
|
||||||
out/
|
out/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
!gradle-wrapper.jar
|
!gradle-wrapper.jar
|
||||||
|
|
||||||
/demo/device-collective/mapCache/
|
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "magix/rfc"]
|
|
||||||
path = magix/rfc
|
|
||||||
url = https://github.com/waltz-controls/rfc
|
|
80
CHANGELOG.md
80
CHANGELOG.md
@ -1,80 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Value averaging plot extension
|
|
||||||
- PLC4X bindings
|
|
||||||
- Shortcuts to access all Controls devices in a magix network.
|
|
||||||
- `DeviceClient` properly evaluates lifecycle and logs
|
|
||||||
- `PeerConnection` API for direct device-device binary sharing
|
|
||||||
- DeviceDrawable2D intermediate visualization implementation
|
|
||||||
- New interface `WithLifeCycle`. Change Port API to adhere to it.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Constructor properties return `DeviceState` in order to be able to subscribe to them
|
|
||||||
- Refactored ports. Now we have `AsynchronousPort` as well as `SynchronousPort`
|
|
||||||
- `DeviceClient` now initializes property and action descriptors eagerly.
|
|
||||||
- `DeviceHub` now works with `Name` instead of `NameToken`. Tree-like structure is made using `Path`. Device messages no longer have access to sub-devices.
|
|
||||||
- Add some utility methods to ports. Synchronous port response could be now consumed as `Source`.
|
|
||||||
- `DeviceLifecycleState` is replaced by `LifecycleState`.
|
|
||||||
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fix a problem with rsocket endpoint with no filter.
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
## 0.3.0 - 2024-03-04
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Device lifecycle message
|
|
||||||
- Low-code constructor
|
|
||||||
- Automatic description generation for spec properties (JVM only)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Property caching moved from core `Device` to the `CachingDevice`
|
|
||||||
- `DeviceSpec` properties no explicitly pass property name to getters and setters.
|
|
||||||
- `DeviceHub.respondHubMessage` now returns a list of messages to allow querying multiple devices. Device server also returns an array.
|
|
||||||
- DataForge 0.8.0
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Property writing does not trigger change if logical state already is the same as value to be set.
|
|
||||||
- Modbus-slave triggers only once for multi-register write.
|
|
||||||
- Removed unnecessary scope in hub messageFlow
|
|
||||||
|
|
||||||
## 0.2.2-dev-1 - 2023-09-24
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- updating logical state in `DeviceBase` is now protected and called `propertyChanged()`
|
|
||||||
- `DeviceBase` tries to read property after write if the writer does not set the value.
|
|
||||||
|
|
||||||
## 0.2.1 - 2023-09-24
|
|
||||||
|
|
||||||
### 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
|
|
241
README.md
241
README.md
@ -1,15 +1,15 @@
|
|||||||
[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||||
|
|
||||||
[](https://maven.sciprog.center/)
|
# DataForge-control
|
||||||
|
|
||||||
# Controls.kt
|
DataForge-control is a data acquisition framework (work in progress). It is based on DataForge, a software framework for automated data processing.
|
||||||
|
|
||||||
Controls.kt (former DataForge-control) is a data acquisition framework (work in progress). It is based on DataForge, a software framework for automated data processing.
|
|
||||||
This repository contains a prototype of API and simple implementation
|
This repository contains a prototype of API and simple implementation
|
||||||
of a slow control system, including a demo.
|
of a slow control system, including a demo.
|
||||||
|
|
||||||
Controls.kt uses some concepts and modules of DataForge,
|
DataForge-control uses some concepts and modules of DataForge,
|
||||||
such as `Meta` (tree-like value structure).
|
such as `Meta` (immutable tree-like structure) and `MetaItem` (which
|
||||||
|
includes a scalar value, or a tree of values, easily convertable to/from JSON
|
||||||
|
if needed).
|
||||||
|
|
||||||
To learn more about DataForge, please consult the following URLs:
|
To learn more about DataForge, please consult the following URLs:
|
||||||
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
|
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
|
||||||
@ -20,11 +20,6 @@ DataForge-control is a [Kotlin-multiplatform](https://kotlinlang.org/docs/refere
|
|||||||
application. Asynchronous operations are implemented with
|
application. Asynchronous operations are implemented with
|
||||||
[kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) library.
|
[kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) library.
|
||||||
|
|
||||||
## Materials and publications
|
|
||||||
|
|
||||||
* Video - [A general overview seminar](https://youtu.be/LO-qjWgXMWc)
|
|
||||||
* Video - [A seminar about the system mechanics](https://youtu.be/wES0RV5GpoQ)
|
|
||||||
* Article - [A Novel Solution for Controlling Hardware Components of Accelerators and Beamlines](https://www.preprints.org/manuscript/202108.0336/v1)
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
Among other things, you can:
|
Among other things, you can:
|
||||||
@ -33,219 +28,19 @@ Among other things, you can:
|
|||||||
- Property values can be cached in the system and requested from devices as needed, asynchronously.
|
- Property values can be cached in the system and requested from devices as needed, asynchronously.
|
||||||
- Connect devices to event bus via bidirectional message flows.
|
- Connect devices to event bus via bidirectional message flows.
|
||||||
|
|
||||||
Example view of a demo:
|
### `dataforge-control-core` module packages
|
||||||
|
|
||||||

|
- `api` - defines API for device management. The main class here is
|
||||||
|
[`Device`](dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt).
|
||||||
|
Generally, a Device has Properties that can be read and written. Also, some Actions
|
||||||
|
can optionally be applied on a device (may or may not affect properties).
|
||||||
|
|
||||||
## Documentation
|
- `base` - contains baseline `Device` implementation
|
||||||
|
[`DeviceBase`](dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt)
|
||||||
* [Creating a device](docs/Device%20and%20DeviceSpec.md)
|
and property implementation, including property asynchronous flows.
|
||||||
|
|
||||||
## Modules
|
|
||||||
|
|
||||||
|
|
||||||
### [controls-constructor](controls-constructor)
|
|
||||||
> A low-code constructor for composite devices simulation
|
|
||||||
>
|
|
||||||
> **Maturity**: PROTOTYPE
|
|
||||||
|
|
||||||
### [controls-core](controls-core)
|
|
||||||
> Core interfaces for building a device server
|
|
||||||
>
|
|
||||||
> **Maturity**: EXPERIMENTAL
|
|
||||||
>
|
|
||||||
> **Features:**
|
|
||||||
> - [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-jupyter](controls-jupyter)
|
|
||||||
>
|
|
||||||
> **Maturity**: EXPERIMENTAL
|
|
||||||
|
|
||||||
### [controls-magix](controls-magix)
|
|
||||||
> Magix service for binding controls devices (both as RPC client and server)
|
|
||||||
>
|
|
||||||
> **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-plc4x](controls-plc4x)
|
|
||||||
> A plugin for Controls-kt device server on top of plc4x library
|
|
||||||
>
|
|
||||||
> **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**: PROTOTYPE
|
|
||||||
|
|
||||||
### [controls-storage](controls-storage)
|
|
||||||
> An API for stand-alone Controls-kt device or a hub.
|
|
||||||
>
|
|
||||||
> **Maturity**: PROTOTYPE
|
|
||||||
|
|
||||||
### [controls-vision](controls-vision)
|
|
||||||
> Dashboard and visualization extensions for devices
|
|
||||||
>
|
|
||||||
> **Maturity**: PROTOTYPE
|
|
||||||
|
|
||||||
### [controls-visualisation-compose](controls-visualisation-compose)
|
|
||||||
> Visualisation extension using compose-multiplatform
|
|
||||||
>
|
|
||||||
> **Maturity**: PROTOTYPE
|
|
||||||
|
|
||||||
### [demo](demo)
|
|
||||||
>
|
|
||||||
> **Maturity**: EXPERIMENTAL
|
|
||||||
|
|
||||||
### [magix](magix)
|
|
||||||
>
|
|
||||||
> **Maturity**: EXPERIMENTAL
|
|
||||||
|
|
||||||
### [simulation-kt](simulation-kt)
|
|
||||||
> A framework for combination of asynchronous simulations.
|
|
||||||
>
|
|
||||||
> **Maturity**: PROTOTYPE
|
|
||||||
>
|
|
||||||
> **Features:**
|
|
||||||
> - [timeline](simulation-kt/#) : Timeline is an ordered discrete history containing TimeLineEvent
|
|
||||||
|
|
||||||
|
|
||||||
### [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/constructor](demo/constructor)
|
|
||||||
>
|
|
||||||
> **Maturity**: EXPERIMENTAL
|
|
||||||
|
|
||||||
### [demo/device-collective](demo/device-collective)
|
|
||||||
>
|
|
||||||
> **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-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**: PROTOTYPE
|
|
||||||
|
|
||||||
### [magix/magix-utils](magix/magix-utils)
|
|
||||||
> Common utilities and services for Magix endpoints.
|
|
||||||
>
|
|
||||||
> **Maturity**: EXPERIMENTAL
|
|
||||||
|
|
||||||
### [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
|
|
||||||
|
|
||||||
|
- `controllers` - implements Message Controller that can be attached to the event bus, Message
|
||||||
|
and Property flows.
|
||||||
|
|
||||||
### `demo` module
|
### `demo` module
|
||||||
|
|
||||||
@ -254,3 +49,7 @@ the current time. The device is configurable via a simple TornadoFX-based contro
|
|||||||
You can run a demo by executing `application/run` Gradle task.
|
You can run a demo by executing `application/run` Gradle task.
|
||||||
|
|
||||||
The graphs are displayed using [plotly.kt](https://github.com/mipt-npm/plotly.kt) library.
|
The graphs are displayed using [plotly.kt](https://github.com/mipt-npm/plotly.kt) library.
|
||||||
|
|
||||||
|
Example view of a demo:
|
||||||
|
|
||||||
|

|
||||||
|
@ -1,26 +1,33 @@
|
|||||||
import space.kscience.gradle.useApache2Licence
|
|
||||||
import space.kscience.gradle.useSPCTeam
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("space.kscience.gradle.project")
|
id("ru.mipt.npm.project")
|
||||||
alias(libs.plugins.versions)
|
kotlin("jvm") apply false
|
||||||
|
kotlin("js") apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val dataforgeVersion: String by extra("0.2.1-dev-2")
|
||||||
|
val ktorVersion: String by extra("1.5.0")
|
||||||
|
val rsocketVersion by extra("0.12.0")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
|
||||||
version = "0.4.0-dev-7"
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
mavenLocal()
|
||||||
|
//maven("https://dl.bintray.com/pdvrieze/maven")
|
||||||
|
//maven("http://maven.jzy3d.org/releases")
|
||||||
|
maven("https://kotlin.bintray.com/js-externals")
|
||||||
|
maven("https://maven.pkg.github.com/altavir/kotlin-logging/")
|
||||||
|
//maven("https://dl.bintray.com/rsocket-admin/RSocket")
|
||||||
|
//maven("https://maven.pkg.github.com/altavir/ktor-client-sse")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group = "hep.dataforge"
|
||||||
|
version = "0.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
ksciencePublish {
|
ksciencePublish {
|
||||||
pom("https://github.com/SciProgCentre/controls-kt") {
|
githubProject = "controls.kt"
|
||||||
useApache2Licence()
|
bintrayRepo = "dataforge"
|
||||||
useSPCTeam()
|
|
||||||
}
|
|
||||||
repository("spc","https://maven.sciprog.center/kscience")
|
|
||||||
sonatype("https://oss.sonatype.org")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
|
apiValidation {
|
||||||
|
validationDisabled = true
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
# Module controls-constructor
|
|
||||||
|
|
||||||
A low-code constructor for composite devices simulation
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
## Artifact:
|
|
||||||
|
|
||||||
The Maven coordinates of this project are `space.kscience:controls-constructor:0.4.0-dev-7`.
|
|
||||||
|
|
||||||
**Gradle Kotlin DSL:**
|
|
||||||
```kotlin
|
|
||||||
repositories {
|
|
||||||
maven("https://repo.kotlin.link")
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("space.kscience:controls-constructor:0.4.0-dev-7")
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,28 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("space.kscience.gradle.mpp")
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
description = """
|
|
||||||
A low-code constructor for composite devices simulation
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
kscience{
|
|
||||||
jvm()
|
|
||||||
js()
|
|
||||||
native()
|
|
||||||
wasm()
|
|
||||||
useCoroutines()
|
|
||||||
useSerialization()
|
|
||||||
commonMain {
|
|
||||||
api(projects.controlsCore)
|
|
||||||
}
|
|
||||||
|
|
||||||
commonTest{
|
|
||||||
implementation(spclibs.logback.classic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readme{
|
|
||||||
maturity = space.kscience.gradle.Maturity.PROTOTYPE
|
|
||||||
}
|
|
241
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt
241
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt
@ -1,241 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.manager.ClockManager
|
|
||||||
import space.kscience.dataforge.context.ContextAware
|
|
||||||
import space.kscience.dataforge.context.request
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A binding that is used to describe device functionality
|
|
||||||
*/
|
|
||||||
public sealed interface ConstructorElement
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A binding that exposes device property as read-only state
|
|
||||||
*/
|
|
||||||
public class PropertyConstructorElement<T>(
|
|
||||||
public val device: Device,
|
|
||||||
public val propertyName: String,
|
|
||||||
public val state: DeviceState<T>,
|
|
||||||
) : ConstructorElement
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A binding for independent state like a timer
|
|
||||||
*/
|
|
||||||
public class StateConstructorElement<T>(
|
|
||||||
public val state: DeviceState<T>,
|
|
||||||
) : ConstructorElement
|
|
||||||
|
|
||||||
public class ConnectionConstrucorElement(
|
|
||||||
public val reads: Collection<DeviceState<*>>,
|
|
||||||
public val writes: Collection<DeviceState<*>>,
|
|
||||||
) : ConstructorElement
|
|
||||||
|
|
||||||
public class ModelConstructorElement(
|
|
||||||
public val model: ModelConstructor,
|
|
||||||
) : ConstructorElement
|
|
||||||
|
|
||||||
|
|
||||||
public interface StateContainer : ContextAware, CoroutineScope {
|
|
||||||
public val constructorElements: Set<ConstructorElement>
|
|
||||||
public fun registerElement(constructorElement: ConstructorElement)
|
|
||||||
public fun unregisterElement(constructorElement: ConstructorElement)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind an action to a [DeviceState]. [onChange] block is performed on each state change
|
|
||||||
*
|
|
||||||
* Optionally provide [writes] - a set of states that this change affects.
|
|
||||||
*/
|
|
||||||
public fun <T> DeviceState<T>.onNext(
|
|
||||||
writes: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
reads: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
onChange: suspend (T) -> Unit,
|
|
||||||
): Job = valueFlow.onEach(onChange).launchIn(this@StateContainer).also {
|
|
||||||
registerElement(ConnectionConstrucorElement(reads + this, writes))
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> DeviceState<T>.onChange(
|
|
||||||
writes: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
reads: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
onChange: suspend (prev: T, next: T) -> Unit,
|
|
||||||
): Job = valueFlow.runningFold(Pair(value, value)) { pair, next ->
|
|
||||||
Pair(pair.second, next)
|
|
||||||
}.onEach { pair ->
|
|
||||||
if (pair.first != pair.second) {
|
|
||||||
onChange(pair.first, pair.second)
|
|
||||||
}
|
|
||||||
}.launchIn(this@StateContainer).also {
|
|
||||||
registerElement(ConnectionConstrucorElement(reads + this, writes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [state] in this container. The state is not registered as a device property if [this] is a [DeviceConstructor]
|
|
||||||
*/
|
|
||||||
public fun <T, D : DeviceState<T>> StateContainer.registerState(state: D): D {
|
|
||||||
registerElement(StateConstructorElement(state))
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a register a [MutableDeviceState]
|
|
||||||
*/
|
|
||||||
public fun <T> StateContainer.stateOf(initialValue: T): MutableDeviceState<T> = registerState(
|
|
||||||
MutableDeviceState(initialValue)
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <T : ModelConstructor> StateContainer.model(model: T): T {
|
|
||||||
registerElement(ModelConstructorElement(model))
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and register a timer state.
|
|
||||||
*/
|
|
||||||
public fun StateContainer.timer(tick: Duration): TimerState =
|
|
||||||
registerState(TimerState(context.request(ClockManager), tick))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a new timer and perform [block] on its change
|
|
||||||
*/
|
|
||||||
public fun StateContainer.onTimer(
|
|
||||||
tick: Duration,
|
|
||||||
writes: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
reads: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
block: suspend (prev: Instant, next: Instant) -> Unit,
|
|
||||||
): Job = timer(tick).onChange(writes = writes, reads = reads, onChange = block)
|
|
||||||
|
|
||||||
public enum class DefaultTimer(public val duration: Duration){
|
|
||||||
REALTIME(5.milliseconds),
|
|
||||||
VERY_FAST(10.milliseconds),
|
|
||||||
FAST(20.milliseconds),
|
|
||||||
MEDIUM(50.milliseconds),
|
|
||||||
SLOW(100.milliseconds),
|
|
||||||
VERY_SLOW(500.milliseconds),
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform an action on default timer
|
|
||||||
*/
|
|
||||||
public fun StateContainer.onTimer(
|
|
||||||
defaultTimer: DefaultTimer = DefaultTimer.FAST,
|
|
||||||
writes: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
reads: Collection<DeviceState<*>> = emptySet(),
|
|
||||||
block: suspend (prev: Instant, next: Instant) -> Unit,
|
|
||||||
): Job = timer(defaultTimer.duration).onChange(writes = writes, reads = reads, onChange = block)
|
|
||||||
//TODO implement timer pooling
|
|
||||||
|
|
||||||
public fun <T, R> StateContainer.mapState(
|
|
||||||
origin: DeviceState<T>,
|
|
||||||
transformation: (T) -> R,
|
|
||||||
): DeviceStateWithDependencies<R> = registerState(DeviceState.map(origin, transformation))
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T, R> StateContainer.flowState(
|
|
||||||
origin: DeviceState<T>,
|
|
||||||
initialValue: R,
|
|
||||||
transformation: suspend FlowCollector<R>.(T) -> Unit,
|
|
||||||
): DeviceStateWithDependencies<R> {
|
|
||||||
val state = MutableDeviceState(initialValue)
|
|
||||||
origin.valueFlow.transform(transformation).onEach { state.value = it }.launchIn(this)
|
|
||||||
return registerState(state.withDependencies(setOf(origin)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new state by combining two existing ones
|
|
||||||
*/
|
|
||||||
public fun <T1, T2, R> StateContainer.combineState(
|
|
||||||
first: DeviceState<T1>,
|
|
||||||
second: DeviceState<T2>,
|
|
||||||
transformation: (T1, T2) -> R,
|
|
||||||
): DeviceState<R> = registerState(DeviceState.combine(first, second, transformation))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and start binding between [sourceState] and [targetState]. Changes made to [sourceState] are automatically
|
|
||||||
* transferred onto [targetState], but not vise versa.
|
|
||||||
*
|
|
||||||
* On resulting [Job] cancel the binding is unregistered
|
|
||||||
*/
|
|
||||||
public fun <T> StateContainer.bind(sourceState: DeviceState<T>, targetState: MutableDeviceState<T>): Job {
|
|
||||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState), setOf(targetState))
|
|
||||||
registerElement(descriptor)
|
|
||||||
return sourceState.valueFlow.onEach {
|
|
||||||
targetState.value = it
|
|
||||||
}.launchIn(this).apply {
|
|
||||||
invokeOnCompletion {
|
|
||||||
unregisterElement(descriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and start binding between [sourceState] and [targetState]. Changes made to [sourceState] are automatically
|
|
||||||
* transferred onto [targetState] via [transformation], but not vise versa.
|
|
||||||
*
|
|
||||||
* On resulting [Job] cancel the binding is unregistered
|
|
||||||
*/
|
|
||||||
public fun <T, R> StateContainer.transformTo(
|
|
||||||
sourceState: DeviceState<T>,
|
|
||||||
targetState: MutableDeviceState<R>,
|
|
||||||
transformation: suspend (T) -> R,
|
|
||||||
): Job {
|
|
||||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState), setOf(targetState))
|
|
||||||
registerElement(descriptor)
|
|
||||||
return sourceState.valueFlow.onEach {
|
|
||||||
targetState.value = transformation(it)
|
|
||||||
}.launchIn(this).apply {
|
|
||||||
invokeOnCompletion {
|
|
||||||
unregisterElement(descriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register [ConstructorElement] that combines values from [sourceState1] and [sourceState2] using [transformation].
|
|
||||||
*
|
|
||||||
* On resulting [Job] cancel the binding is unregistered
|
|
||||||
*/
|
|
||||||
public fun <T1, T2, R> StateContainer.combineTo(
|
|
||||||
sourceState1: DeviceState<T1>,
|
|
||||||
sourceState2: DeviceState<T2>,
|
|
||||||
targetState: MutableDeviceState<R>,
|
|
||||||
transformation: suspend (T1, T2) -> R,
|
|
||||||
): Job {
|
|
||||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState1, sourceState2), setOf(targetState))
|
|
||||||
registerElement(descriptor)
|
|
||||||
return kotlinx.coroutines.flow.combine(sourceState1.valueFlow, sourceState2.valueFlow, transformation).onEach {
|
|
||||||
targetState.value = it
|
|
||||||
}.launchIn(this).apply {
|
|
||||||
invokeOnCompletion {
|
|
||||||
unregisterElement(descriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register [ConstructorElement] that combines values from [sourceStates] using [transformation].
|
|
||||||
*
|
|
||||||
* On resulting [Job] cancel the binding is unregistered
|
|
||||||
*/
|
|
||||||
public inline fun <reified T, R> StateContainer.combineTo(
|
|
||||||
sourceStates: Collection<DeviceState<T>>,
|
|
||||||
targetState: MutableDeviceState<R>,
|
|
||||||
noinline transformation: suspend (Array<T>) -> R,
|
|
||||||
): Job {
|
|
||||||
val descriptor = ConnectionConstrucorElement(sourceStates, setOf(targetState))
|
|
||||||
registerElement(descriptor)
|
|
||||||
return kotlinx.coroutines.flow.combine(sourceStates.map { it.valueFlow }, transformation).onEach {
|
|
||||||
targetState.value = it
|
|
||||||
}.launchIn(this).apply {
|
|
||||||
invokeOnCompletion {
|
|
||||||
unregisterElement(descriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
150
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt
150
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/DeviceConstructor.kt
@ -1,150 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A base for strongly typed device constructor block. Has additional delegates for type-safe devices
|
|
||||||
*/
|
|
||||||
public abstract class DeviceConstructor(
|
|
||||||
context: Context,
|
|
||||||
meta: Meta = Meta.EMPTY,
|
|
||||||
) : DeviceGroup(context, meta), StateContainer {
|
|
||||||
private val _constructorElements: MutableSet<ConstructorElement> = mutableSetOf()
|
|
||||||
override val constructorElements: Set<ConstructorElement> get() = _constructorElements
|
|
||||||
|
|
||||||
override fun registerElement(constructorElement: ConstructorElement) {
|
|
||||||
_constructorElements.add(constructorElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun unregisterElement(constructorElement: ConstructorElement) {
|
|
||||||
_constructorElements.remove(constructorElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T, S: DeviceState<T>> registerProperty(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptor: PropertyDescriptor,
|
|
||||||
state: S,
|
|
||||||
): S {
|
|
||||||
val res = super.registerProperty(converter, descriptor, state)
|
|
||||||
registerElement(PropertyConstructorElement(this, descriptor.name, state))
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a device, provided by a given [factory] and
|
|
||||||
*/
|
|
||||||
public fun <D : Device> DeviceConstructor.device(
|
|
||||||
factory: Factory<D>,
|
|
||||||
meta: Meta? = null,
|
|
||||||
nameOverride: Name? = null,
|
|
||||||
metaLocation: Name? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
|
|
||||||
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
|
|
||||||
val name = nameOverride ?: property.name.asName()
|
|
||||||
val device = install(name, factory, meta, metaLocation ?: name)
|
|
||||||
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
|
||||||
device
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceConstructor.device(
|
|
||||||
device: D,
|
|
||||||
nameOverride: Name? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
|
|
||||||
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
|
|
||||||
val name = nameOverride ?: property.name.asName()
|
|
||||||
install(name, device)
|
|
||||||
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
|
||||||
device
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a property and provide a direct reader for it
|
|
||||||
*/
|
|
||||||
public fun <T, S : DeviceState<T>> DeviceConstructor.property(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
state: S,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
nameOverride: String? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, S>> =
|
|
||||||
PropertyDelegateProvider { _: DeviceConstructor, property ->
|
|
||||||
val name = nameOverride ?: property.name
|
|
||||||
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
|
|
||||||
registerProperty(converter, descriptor, state)
|
|
||||||
ReadOnlyProperty { _: DeviceConstructor, _ ->
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register external state as a property
|
|
||||||
*/
|
|
||||||
public fun <T : Any> DeviceConstructor.property(
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
reader: suspend () -> T,
|
|
||||||
readInterval: Duration,
|
|
||||||
initialState: T,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
nameOverride: String? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, DeviceState<T>>> = property(
|
|
||||||
metaConverter,
|
|
||||||
DeviceState.external(this, readInterval, initialState, reader),
|
|
||||||
descriptorBuilder,
|
|
||||||
nameOverride,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and register a mutable external state as a property
|
|
||||||
*/
|
|
||||||
public fun <T : Any> DeviceConstructor.mutableProperty(
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
reader: suspend () -> T,
|
|
||||||
writer: suspend (T) -> Unit,
|
|
||||||
readInterval: Duration,
|
|
||||||
initialState: T,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
nameOverride: String? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
|
|
||||||
metaConverter,
|
|
||||||
DeviceState.external(this, readInterval, initialState, reader, writer),
|
|
||||||
descriptorBuilder,
|
|
||||||
nameOverride,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and register a virtual mutable property with optional [callback]
|
|
||||||
*/
|
|
||||||
public fun <T> DeviceConstructor.virtualProperty(
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
initialState: T,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
nameOverride: String? = null,
|
|
||||||
callback: (T) -> Unit = {},
|
|
||||||
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
|
|
||||||
metaConverter,
|
|
||||||
MutableDeviceState(initialState, callback),
|
|
||||||
descriptorBuilder,
|
|
||||||
nameOverride,
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <T, S : DeviceState<T>> DeviceConstructor.registerAsProperty(
|
|
||||||
spec: DevicePropertySpec<*, T>,
|
|
||||||
state: S,
|
|
||||||
): S {
|
|
||||||
registerProperty(spec.converter, spec.descriptor, state)
|
|
||||||
return state
|
|
||||||
}
|
|
@ -1,318 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import space.kscience.controls.api.*
|
|
||||||
import space.kscience.controls.api.LifecycleState.*
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.controls.manager.install
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.dataforge.context.*
|
|
||||||
import space.kscience.dataforge.meta.Laminate
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.meta.MutableMeta
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.get
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
import kotlin.collections.set
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mutable group of devices and properties to be used for lightweight design and simulations.
|
|
||||||
*/
|
|
||||||
public open class DeviceGroup(
|
|
||||||
final override val context: Context,
|
|
||||||
override val meta: Meta,
|
|
||||||
) : DeviceHub, CachingDevice {
|
|
||||||
|
|
||||||
private class Property<T>(
|
|
||||||
val state: DeviceState<T>,
|
|
||||||
val converter: MetaConverter<T>,
|
|
||||||
val descriptor: PropertyDescriptor,
|
|
||||||
) {
|
|
||||||
val valueAsMeta get() = converter.convert(state.value)
|
|
||||||
|
|
||||||
fun setMeta(meta: Meta) {
|
|
||||||
check(state is MutableDeviceState) { "Can't write to read-only property" }
|
|
||||||
|
|
||||||
state.value = converter.read(meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Action<T, R>(
|
|
||||||
val inputConverter: MetaConverter<T>,
|
|
||||||
val outputConverter: MetaConverter<R>,
|
|
||||||
val descriptor: ActionDescriptor,
|
|
||||||
val action: suspend (T) -> R,
|
|
||||||
) {
|
|
||||||
suspend operator fun invoke(argument: Meta?): Meta? = argument?.let { inputConverter.readOrNull(it) }
|
|
||||||
?.let { action(it)?.let { outputConverter.convert(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
|
|
||||||
|
|
||||||
override val messageFlow: Flow<DeviceMessage>
|
|
||||||
get() = sharedMessageFlow
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
override val coroutineContext: CoroutineContext = context.newCoroutineContext(
|
|
||||||
SupervisorJob(context.coroutineContext[Job]) +
|
|
||||||
CoroutineName("Device $id") +
|
|
||||||
CoroutineExceptionHandler { _, throwable ->
|
|
||||||
context.launch {
|
|
||||||
sharedMessageFlow.emit(
|
|
||||||
DeviceErrorMessage(
|
|
||||||
errorMessage = throwable.message,
|
|
||||||
errorType = throwable::class.simpleName,
|
|
||||||
errorStackTrace = throwable.stackTraceToString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
logger.error(throwable) { "Exception in device $id" }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
private val _devices = hashMapOf<Name, Device>()
|
|
||||||
|
|
||||||
override val devices: Map<Name, Device> = _devices
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register and initialize (synchronize child's lifecycle state with group state) a new device in this group
|
|
||||||
*/
|
|
||||||
@OptIn(DFExperimental::class)
|
|
||||||
public open fun <D : Device> install(token: Name, device: D): D {
|
|
||||||
require(_devices[token] == null) { "A child device with name $token already exists" }
|
|
||||||
//start the child device if needed
|
|
||||||
if (lifecycleState == STARTED || lifecycleState == STARTING) launch { device.start() }
|
|
||||||
_devices[token] = device
|
|
||||||
return device
|
|
||||||
}
|
|
||||||
|
|
||||||
private val properties: MutableMap<Name, Property<*>> = hashMapOf()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a new property based on [DeviceState]. Properties could be modified dynamically
|
|
||||||
*/
|
|
||||||
public open fun <T, S : DeviceState<T>> registerProperty(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptor: PropertyDescriptor,
|
|
||||||
state: S,
|
|
||||||
): S {
|
|
||||||
val name = descriptor.name.parseAsName()
|
|
||||||
require(properties[name] == null) { "Can't add property with name $name. It already exists." }
|
|
||||||
properties[name] = Property(state, converter, descriptor)
|
|
||||||
state.valueFlow.map(converter::convert).onEach {
|
|
||||||
sharedMessageFlow.emit(
|
|
||||||
PropertyChangedMessage(
|
|
||||||
descriptor.name,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}.launchIn(this)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
private val actions: MutableMap<Name, Action<*, *>> = hashMapOf()
|
|
||||||
|
|
||||||
public fun <T, R> registerAction(
|
|
||||||
inputConverter: MetaConverter<T>,
|
|
||||||
outputConverter: MetaConverter<R>,
|
|
||||||
descriptor: ActionDescriptor,
|
|
||||||
action: suspend (T) -> R,
|
|
||||||
): suspend (T) -> R {
|
|
||||||
val name = descriptor.name.parseAsName()
|
|
||||||
require(actions[name] == null) { "Can't add action with name $name. It already exists." }
|
|
||||||
actions[name] = Action(
|
|
||||||
inputConverter = inputConverter,
|
|
||||||
outputConverter = outputConverter,
|
|
||||||
descriptor = descriptor,
|
|
||||||
action = action
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
action(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
|
||||||
get() = properties.values.map { it.descriptor }
|
|
||||||
|
|
||||||
override val actionDescriptors: Collection<ActionDescriptor>
|
|
||||||
get() = actions.values.map { it.descriptor }
|
|
||||||
|
|
||||||
override suspend fun readProperty(propertyName: String): Meta =
|
|
||||||
properties[propertyName.parseAsName()]?.valueAsMeta
|
|
||||||
?: error("Property with name $propertyName not found")
|
|
||||||
|
|
||||||
override fun getProperty(propertyName: String): Meta? = properties[propertyName.parseAsName()]?.valueAsMeta
|
|
||||||
|
|
||||||
override suspend fun invalidate(propertyName: String) {
|
|
||||||
//does nothing for this implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeProperty(propertyName: String, value: Meta) {
|
|
||||||
val property = properties[propertyName.parseAsName()] ?: error("Property with name $propertyName not found")
|
|
||||||
property.setMeta(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
|
|
||||||
val action: Action<*, *> = actions[actionName] ?: error("Action with name $actionName not found")
|
|
||||||
return action(argument)
|
|
||||||
}
|
|
||||||
|
|
||||||
final override var lifecycleState: LifecycleState = LifecycleState.STOPPED
|
|
||||||
private set
|
|
||||||
|
|
||||||
|
|
||||||
private suspend fun setLifecycleState(lifecycleState: LifecycleState) {
|
|
||||||
this.lifecycleState = lifecycleState
|
|
||||||
sharedMessageFlow.emit(
|
|
||||||
DeviceLifeCycleMessage(lifecycleState)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun start() {
|
|
||||||
setLifecycleState(STARTING)
|
|
||||||
super.start()
|
|
||||||
devices.values.forEach {
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
setLifecycleState(STARTED)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun stop() {
|
|
||||||
devices.values.forEach {
|
|
||||||
it.stop()
|
|
||||||
}
|
|
||||||
setLifecycleState(STOPPED)
|
|
||||||
super.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> DeviceGroup.registerAsProperty(propertySpec: DevicePropertySpec<*, T>, state: DeviceState<T>) {
|
|
||||||
registerProperty(propertySpec.converter, propertySpec.descriptor, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun DeviceManager.registerDeviceGroup(
|
|
||||||
name: String = "@group",
|
|
||||||
meta: Meta = Meta.EMPTY,
|
|
||||||
block: DeviceGroup.() -> Unit,
|
|
||||||
): DeviceGroup {
|
|
||||||
val group = DeviceGroup(context, meta).apply(block)
|
|
||||||
install(name, group)
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Context.registerDeviceGroup(
|
|
||||||
name: String = "@group",
|
|
||||||
meta: Meta = Meta.EMPTY,
|
|
||||||
block: DeviceGroup.() -> Unit,
|
|
||||||
): DeviceGroup = request(DeviceManager).registerDeviceGroup(name, meta, block)
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Register a device at given [path] path
|
|
||||||
// */
|
|
||||||
//public fun <D : Device> DeviceGroup.install(path: Path, device: D): D {
|
|
||||||
// return when (path.length) {
|
|
||||||
// 0 -> error("Can't use empty path for a child device")
|
|
||||||
// 1 -> install(path.first().name, device)
|
|
||||||
// else -> getOrCreateGroup(path.cutLast()).install(path.tokens.last(), device)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceGroup.install(name: String, device: D): D = install(name.parseAsName(), device)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceGroup.install(device: D): D = install(device.id, device)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a device creating intermediate groups if necessary. If device with given [name] already exists, throws an error.
|
|
||||||
* @param name the name of the device in the group
|
|
||||||
* @param factory a factory used to create a device
|
|
||||||
* @param deviceMeta meta override for this specific device
|
|
||||||
* @param metaLocation location of the template meta in parent group meta
|
|
||||||
*/
|
|
||||||
public fun <D : Device> DeviceGroup.install(
|
|
||||||
name: Name,
|
|
||||||
factory: Factory<D>,
|
|
||||||
deviceMeta: Meta? = null,
|
|
||||||
metaLocation: Name = name,
|
|
||||||
): D {
|
|
||||||
val newDevice = factory.build(context, Laminate(deviceMeta, meta[metaLocation]))
|
|
||||||
install(name, newDevice)
|
|
||||||
return newDevice
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceGroup.install(
|
|
||||||
name: String,
|
|
||||||
factory: Factory<D>,
|
|
||||||
metaLocation: Name = name.parseAsName(),
|
|
||||||
metaBuilder: (MutableMeta.() -> Unit)? = null,
|
|
||||||
): D = install(name.parseAsName(), factory, metaBuilder?.let { Meta(it) }, metaLocation)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create or edit a group with a given [name].
|
|
||||||
*/
|
|
||||||
public fun DeviceGroup.registerDeviceGroup(name: Name, block: DeviceGroup.() -> Unit): DeviceGroup =
|
|
||||||
install(name, DeviceGroup(context, meta).apply(block))
|
|
||||||
|
|
||||||
public fun DeviceGroup.registerDeviceGroup(name: String, block: DeviceGroup.() -> Unit): DeviceGroup =
|
|
||||||
registerDeviceGroup(name.parseAsName(), block)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register read-only property based on [state]
|
|
||||||
*/
|
|
||||||
public fun <T : Any> DeviceGroup.registerAsProperty(
|
|
||||||
name: String,
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
state: DeviceState<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
) {
|
|
||||||
registerProperty(
|
|
||||||
converter,
|
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
|
||||||
state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a mutable property based on mutable [state]
|
|
||||||
*/
|
|
||||||
public fun <T : Any> DeviceGroup.registerMutableProperty(
|
|
||||||
name: String,
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
state: MutableDeviceState<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
) {
|
|
||||||
registerProperty(
|
|
||||||
converter,
|
|
||||||
PropertyDescriptor(name).apply(descriptorBuilder),
|
|
||||||
state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new virtual mutable state and a property based on it.
|
|
||||||
* @return the mutable state used in property
|
|
||||||
*/
|
|
||||||
public fun <T : Any> DeviceGroup.registerVirtualProperty(
|
|
||||||
name: String,
|
|
||||||
initialValue: T,
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
callback: (T) -> Unit = {},
|
|
||||||
): MutableDeviceState<T> {
|
|
||||||
val state = MutableDeviceState<T>(initialValue, callback)
|
|
||||||
registerMutableProperty(name, converter, state, descriptorBuilder)
|
|
||||||
return state
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.UnitsOfMeasurement
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An observable state of a device
|
|
||||||
*/
|
|
||||||
public interface DeviceState<out T> {
|
|
||||||
public val value: T
|
|
||||||
|
|
||||||
public val valueFlow: Flow<T>
|
|
||||||
|
|
||||||
override fun toString(): String
|
|
||||||
|
|
||||||
public companion object
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public operator fun <T> DeviceState<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect values in a given [scope]
|
|
||||||
*/
|
|
||||||
public fun <T> DeviceState<T>.collectValuesIn(scope: CoroutineScope, block: suspend (T) -> Unit): Job =
|
|
||||||
valueFlow.onEach(block).launchIn(scope)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mutable state of a device
|
|
||||||
*/
|
|
||||||
public interface MutableDeviceState<T> : DeviceState<T> {
|
|
||||||
override var value: T
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun <T> MutableDeviceState<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
|
||||||
this.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Device state with a value that depends on other device states
|
|
||||||
*/
|
|
||||||
public interface DeviceStateWithDependencies<T> : DeviceState<T> {
|
|
||||||
public val dependencies: Collection<DeviceState<*>>
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> DeviceState<T>.withDependencies(
|
|
||||||
dependencies: Collection<DeviceState<*>>,
|
|
||||||
): DeviceStateWithDependencies<T> = object : DeviceStateWithDependencies<T>, DeviceState<T> by this {
|
|
||||||
override val dependencies: Collection<DeviceState<*>> = dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new read-only [DeviceState] that mirrors receiver state by mapping the value with [mapper].
|
|
||||||
*/
|
|
||||||
public fun <T, R> DeviceState.Companion.map(
|
|
||||||
state: DeviceState<T>,
|
|
||||||
mapper: (T) -> R,
|
|
||||||
): DeviceStateWithDependencies<R> = object : DeviceStateWithDependencies<R> {
|
|
||||||
override val dependencies = listOf(state)
|
|
||||||
|
|
||||||
override val value: R get() = mapper(state.value)
|
|
||||||
|
|
||||||
override val valueFlow: Flow<R> = state.valueFlow.map(mapper)
|
|
||||||
|
|
||||||
override fun toString(): String = "DeviceState.map(state=${state})"
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T, R> DeviceState<T>.map(mapper: (T) -> R): DeviceStateWithDependencies<R> = DeviceState.map(this, mapper)
|
|
||||||
|
|
||||||
public fun DeviceState<NumericalValue<out UnitsOfMeasurement>>.values(): DeviceState<Double> =
|
|
||||||
object : DeviceState<Double> {
|
|
||||||
override val value: Double
|
|
||||||
get() = this@values.value.value
|
|
||||||
|
|
||||||
override val valueFlow: Flow<Double>
|
|
||||||
get() = this@values.valueFlow.map { it.value }
|
|
||||||
|
|
||||||
override fun toString(): String = this@values.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combine two device states into one read-only [DeviceState]. Only the latest value of each state is used.
|
|
||||||
*/
|
|
||||||
public fun <T1, T2, R> DeviceState.Companion.combine(
|
|
||||||
state1: DeviceState<T1>,
|
|
||||||
state2: DeviceState<T2>,
|
|
||||||
mapper: (T1, T2) -> R,
|
|
||||||
): DeviceStateWithDependencies<R> = object : DeviceStateWithDependencies<R> {
|
|
||||||
override val dependencies = listOf(state1, state2)
|
|
||||||
|
|
||||||
override val value: R get() = mapper(state1.value, state2.value)
|
|
||||||
|
|
||||||
override val valueFlow: Flow<R> = kotlinx.coroutines.flow.combine(state1.valueFlow, state2.valueFlow, mapper)
|
|
||||||
|
|
||||||
override fun toString(): String = "DeviceState.combine(state1=$state1, state2=$state2)"
|
|
||||||
}
|
|
33
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ModelConstructor.kt
33
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ModelConstructor.kt
@ -1,33 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.newCoroutineContext
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
public abstract class ModelConstructor(
|
|
||||||
final override val context: Context,
|
|
||||||
vararg dependencies: DeviceState<*>,
|
|
||||||
) : StateContainer, CoroutineScope {
|
|
||||||
|
|
||||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
|
||||||
override val coroutineContext: CoroutineContext = context.newCoroutineContext(SupervisorJob())
|
|
||||||
|
|
||||||
|
|
||||||
private val _constructorElements: MutableSet<ConstructorElement> = mutableSetOf<ConstructorElement>().apply {
|
|
||||||
dependencies.forEach {
|
|
||||||
add(StateConstructorElement(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val constructorElements: Set<ConstructorElement> get() = _constructorElements
|
|
||||||
|
|
||||||
override fun registerElement(constructorElement: ConstructorElement) {
|
|
||||||
_constructorElements.add(constructorElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun unregisterElement(constructorElement: ConstructorElement) {
|
|
||||||
_constructorElements.remove(constructorElement)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import space.kscience.controls.manager.ClockManager
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dedicated [DeviceState] that operates with time.
|
|
||||||
* The state changes with [tick] interval and always shows the time of the last update.
|
|
||||||
*
|
|
||||||
* Both [tick] and current time are computed by [clockManager] enabling time manipulation.
|
|
||||||
*
|
|
||||||
* The timer runs indefinitely until the parent context is closed
|
|
||||||
*/
|
|
||||||
public class TimerState(
|
|
||||||
public val clockManager: ClockManager,
|
|
||||||
public val tick: Duration,
|
|
||||||
) : DeviceState<Instant> {
|
|
||||||
|
|
||||||
private val clock = MutableStateFlow(clockManager.clock.now())
|
|
||||||
|
|
||||||
private val updateJob = clockManager.context.launch(clockManager.asDispatcher()) {
|
|
||||||
while (isActive) {
|
|
||||||
delay(tick)
|
|
||||||
clock.value = clockManager.clock.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val valueFlow: Flow<Instant> get() = clock
|
|
||||||
|
|
||||||
override val value: Instant get() = clock.value
|
|
||||||
|
|
||||||
override fun toString(): String = "TimerState(tick=$tick)"
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.PropertyChangedMessage
|
|
||||||
import space.kscience.controls.api.id
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.MutableDevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.name
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A copy-free [DeviceState] bound to a device property
|
|
||||||
*/
|
|
||||||
private open class BoundDeviceState<T>(
|
|
||||||
val converter: MetaConverter<T>,
|
|
||||||
val device: Device,
|
|
||||||
val propertyName: String,
|
|
||||||
initialValue: T,
|
|
||||||
) : DeviceState<T> {
|
|
||||||
|
|
||||||
override val valueFlow: StateFlow<T> = device.messageFlow.filterIsInstance<PropertyChangedMessage>().filter {
|
|
||||||
it.property == propertyName
|
|
||||||
}.mapNotNull {
|
|
||||||
converter.read(it.value)
|
|
||||||
}.stateIn(device.context, SharingStarted.Eagerly, initialValue)
|
|
||||||
|
|
||||||
override val value: T get() = valueFlow.value
|
|
||||||
override fun toString(): String =
|
|
||||||
"BoundDeviceState(converter=$converter, device=${device.id}, propertyName='$propertyName')"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> Device.propertyAsState(
|
|
||||||
propertyName: String,
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
initialValue: T,
|
|
||||||
): DeviceState<T> = BoundDeviceState(metaConverter, this, propertyName, initialValue)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind a read-only [DeviceState] to a [Device] property
|
|
||||||
*/
|
|
||||||
public suspend fun <T> Device.propertyAsState(
|
|
||||||
propertyName: String,
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
): DeviceState<T> = propertyAsState(
|
|
||||||
propertyName,
|
|
||||||
metaConverter,
|
|
||||||
metaConverter.readOrNull(readProperty(propertyName)) ?: error("Conversion of property failed")
|
|
||||||
)
|
|
||||||
|
|
||||||
public suspend fun <D : Device, T> D.propertyAsState(
|
|
||||||
propertySpec: DevicePropertySpec<D, T>,
|
|
||||||
): DeviceState<T> = propertyAsState(propertySpec.name, propertySpec.converter)
|
|
||||||
|
|
||||||
public fun <D : Device, T> D.propertyAsState(
|
|
||||||
propertySpec: DevicePropertySpec<D, T>,
|
|
||||||
initialValue: T,
|
|
||||||
): DeviceState<T> = propertyAsState(propertySpec.name, propertySpec.converter, initialValue)
|
|
||||||
|
|
||||||
|
|
||||||
private class MutableBoundDeviceState<T>(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
device: Device,
|
|
||||||
propertyName: String,
|
|
||||||
initialValue: T,
|
|
||||||
) : BoundDeviceState<T>(converter, device, propertyName, initialValue), MutableDeviceState<T> {
|
|
||||||
|
|
||||||
override var value: T
|
|
||||||
get() = valueFlow.value
|
|
||||||
set(newValue) {
|
|
||||||
device.launch {
|
|
||||||
device.writeProperty(propertyName, converter.convert(newValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> Device.mutablePropertyAsState(
|
|
||||||
propertyName: String,
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
initialValue: T,
|
|
||||||
): MutableDeviceState<T> = MutableBoundDeviceState(metaConverter, this, propertyName, initialValue)
|
|
||||||
|
|
||||||
public suspend fun <T> Device.mutablePropertyAsState(
|
|
||||||
propertyName: String,
|
|
||||||
metaConverter: MetaConverter<T>,
|
|
||||||
): MutableDeviceState<T> {
|
|
||||||
val initialValue = metaConverter.readOrNull(readProperty(propertyName)) ?: error("Conversion of property failed")
|
|
||||||
return mutablePropertyAsState(propertyName, metaConverter, initialValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun <D : Device, T> D.propertyAsState(
|
|
||||||
propertySpec: MutableDevicePropertySpec<D, T>,
|
|
||||||
): MutableDeviceState<T> = mutablePropertyAsState(propertySpec.name, propertySpec.converter)
|
|
||||||
|
|
||||||
public fun <D : Device, T> D.propertyAsState(
|
|
||||||
propertySpec: MutableDevicePropertySpec<D, T>,
|
|
||||||
initialValue: T,
|
|
||||||
): MutableDeviceState<T> = mutablePropertyAsState(propertySpec.name, propertySpec.converter, initialValue)
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.devices
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.DeviceConstructor
|
|
||||||
import space.kscience.controls.constructor.MutableDeviceState
|
|
||||||
import space.kscience.controls.constructor.property
|
|
||||||
import space.kscience.controls.constructor.units.NewtonsMeters
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.numerical
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
|
|
||||||
//TODO use current as input
|
|
||||||
|
|
||||||
public class Drive(
|
|
||||||
context: Context,
|
|
||||||
force: MutableDeviceState<NumericalValue<NewtonsMeters>> = MutableDeviceState(NumericalValue(0)),
|
|
||||||
) : DeviceConstructor(context) {
|
|
||||||
public val force: MutableDeviceState<NumericalValue<NewtonsMeters>> by property(MetaConverter.numerical(), force)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.devices
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.DeviceConstructor
|
|
||||||
import space.kscience.controls.constructor.DeviceState
|
|
||||||
import space.kscience.controls.constructor.property
|
|
||||||
import space.kscience.controls.constructor.units.Degrees
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.numerical
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An encoder that can read an angle
|
|
||||||
*/
|
|
||||||
public class EncoderDevice(
|
|
||||||
context: Context,
|
|
||||||
position: DeviceState<NumericalValue<Degrees>>
|
|
||||||
) : DeviceConstructor(context) {
|
|
||||||
public val position: DeviceState<NumericalValue<Degrees>> by property(MetaConverter.numerical<Degrees>(), position)
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.devices
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.DeviceConstructor
|
|
||||||
import space.kscience.controls.constructor.DeviceState
|
|
||||||
import space.kscience.controls.constructor.map
|
|
||||||
import space.kscience.controls.constructor.registerAsProperty
|
|
||||||
import space.kscience.controls.constructor.units.Direction
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.UnitsOfMeasurement
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.DeviceSpec
|
|
||||||
import space.kscience.controls.spec.booleanProperty
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A device that detects if a motor hits the end of its range
|
|
||||||
*/
|
|
||||||
public class LimitSwitch(
|
|
||||||
context: Context,
|
|
||||||
locked: DeviceState<Boolean>,
|
|
||||||
) : DeviceConstructor(context) {
|
|
||||||
|
|
||||||
public val locked: DeviceState<Boolean> = registerAsProperty(LimitSwitch.locked, locked)
|
|
||||||
|
|
||||||
public companion object : DeviceSpec<LimitSwitch>() {
|
|
||||||
public val locked: DevicePropertySpec<LimitSwitch, Boolean> by booleanProperty { locked.value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <U : UnitsOfMeasurement, T : NumericalValue<U>> LimitSwitch(
|
|
||||||
context: Context,
|
|
||||||
limit: T,
|
|
||||||
boundary: Direction,
|
|
||||||
position: DeviceState<T>,
|
|
||||||
): LimitSwitch = LimitSwitch(
|
|
||||||
context,
|
|
||||||
DeviceState.map(position) {
|
|
||||||
when (boundary) {
|
|
||||||
Direction.UP -> it >= limit
|
|
||||||
Direction.DOWN -> it <= limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,38 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.devices
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.*
|
|
||||||
import space.kscience.controls.constructor.models.PidParameters
|
|
||||||
import space.kscience.controls.constructor.models.PidRegulator
|
|
||||||
import space.kscience.controls.constructor.units.Meters
|
|
||||||
import space.kscience.controls.constructor.units.NewtonsMeters
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.numerical
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
|
|
||||||
public class LinearDrive(
|
|
||||||
drive: Drive,
|
|
||||||
start: LimitSwitch,
|
|
||||||
end: LimitSwitch,
|
|
||||||
position: DeviceState<NumericalValue<Meters>>,
|
|
||||||
pidParameters: PidParameters,
|
|
||||||
context: Context = drive.context,
|
|
||||||
meta: Meta = Meta.EMPTY,
|
|
||||||
) : DeviceConstructor(context, meta) {
|
|
||||||
|
|
||||||
public val position: DeviceState<NumericalValue<Meters>> by property(MetaConverter.numerical(), position)
|
|
||||||
|
|
||||||
public val drive: Drive by device(drive)
|
|
||||||
public val pid: PidRegulator<Meters, NewtonsMeters> = model(
|
|
||||||
PidRegulator(
|
|
||||||
context = context,
|
|
||||||
position = position,
|
|
||||||
output = drive.force,
|
|
||||||
pidParameters = pidParameters
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
public val startLimit: LimitSwitch by device(start)
|
|
||||||
public val endLimit: LimitSwitch by device(end)
|
|
||||||
}
|
|
65
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/devices/StepDrive.kt
65
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/devices/StepDrive.kt
@ -1,65 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.devices
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.*
|
|
||||||
import space.kscience.controls.constructor.units.Degrees
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.plus
|
|
||||||
import space.kscience.controls.constructor.units.times
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.math.roundToLong
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step drive
|
|
||||||
*
|
|
||||||
* @param ticksPerSecond ticks per second
|
|
||||||
* @param target target ticks state
|
|
||||||
* @param writeTicks a hardware callback
|
|
||||||
*/
|
|
||||||
public class StepDrive(
|
|
||||||
context: Context,
|
|
||||||
ticksPerSecond: Double,
|
|
||||||
position: MutableDeviceState<Long> = MutableDeviceState(0),
|
|
||||||
private val writeTicks: suspend (ticks: Long, speed: Double) -> Unit = { _, _ -> },
|
|
||||||
) : DeviceConstructor(context) {
|
|
||||||
|
|
||||||
public val target: MutableDeviceState<Long> by property(
|
|
||||||
MetaConverter.long,
|
|
||||||
MutableDeviceState<Long>(position.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
public val speed: MutableDeviceState<Double> by property(
|
|
||||||
MetaConverter.double,
|
|
||||||
MutableDeviceState<Double>(ticksPerSecond)
|
|
||||||
)
|
|
||||||
|
|
||||||
public val position: DeviceState<Long> by property(MetaConverter.long, position)
|
|
||||||
|
|
||||||
//FIXME round to zero problem
|
|
||||||
private val ticker = onTimer(reads = setOf(target, position), writes = setOf(position)) { prev, next ->
|
|
||||||
val tickSpeed = speed.value
|
|
||||||
val timeDelta = (next - prev).toDouble(DurationUnit.SECONDS)
|
|
||||||
val ticksDelta: Long = target.value - position.value
|
|
||||||
val steps: Long = when {
|
|
||||||
ticksDelta > 0 -> min(ticksDelta, (timeDelta * tickSpeed).roundToLong())
|
|
||||||
ticksDelta < 0 -> max(ticksDelta, -(timeDelta * tickSpeed).roundToLong())
|
|
||||||
else -> return@onTimer
|
|
||||||
}
|
|
||||||
writeTicks(steps, tickSpeed)
|
|
||||||
position.value += steps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute a state using given tick-to-angle transformation
|
|
||||||
*/
|
|
||||||
public fun StepDrive.angle(
|
|
||||||
step: NumericalValue<Degrees>,
|
|
||||||
zero: NumericalValue<Degrees> = NumericalValue(0),
|
|
||||||
): DeviceState<NumericalValue<Degrees>> = position.map {
|
|
||||||
zero + it * step
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
|
|
||||||
private open class ExternalState<T>(
|
|
||||||
val scope: CoroutineScope,
|
|
||||||
val readInterval: Duration,
|
|
||||||
initialValue: T,
|
|
||||||
val reader: suspend () -> T,
|
|
||||||
) : DeviceState<T> {
|
|
||||||
|
|
||||||
protected val flow: StateFlow<T> = flow {
|
|
||||||
while (true) {
|
|
||||||
delay(readInterval)
|
|
||||||
emit(reader())
|
|
||||||
}
|
|
||||||
}.stateIn(scope, SharingStarted.Eagerly, initialValue)
|
|
||||||
|
|
||||||
override val value: T get() = flow.value
|
|
||||||
override val valueFlow: Flow<T> get() = flow
|
|
||||||
|
|
||||||
override fun toString(): String = "ExternalState()"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [DeviceState] which is constructed by regularly reading external value
|
|
||||||
*/
|
|
||||||
public fun <T> DeviceState.Companion.external(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
readInterval: Duration,
|
|
||||||
initialValue: T,
|
|
||||||
reader: suspend () -> T,
|
|
||||||
): DeviceState<T> = ExternalState(scope, readInterval, initialValue, reader)
|
|
||||||
|
|
||||||
private class MutableExternalState<T>(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
readInterval: Duration,
|
|
||||||
initialValue: T,
|
|
||||||
reader: suspend () -> T,
|
|
||||||
val writer: suspend (T) -> Unit,
|
|
||||||
) : ExternalState<T>(scope, readInterval, initialValue, reader), MutableDeviceState<T> {
|
|
||||||
override var value: T
|
|
||||||
get() = super.value
|
|
||||||
set(value) {
|
|
||||||
scope.launch {
|
|
||||||
writer(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [MutableDeviceState] which is constructed by regularly reading external value and allows writing
|
|
||||||
*/
|
|
||||||
public fun <T> DeviceState.Companion.external(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
readInterval: Duration,
|
|
||||||
initialValue: T,
|
|
||||||
reader: suspend () -> T,
|
|
||||||
writer: suspend (T) -> Unit,
|
|
||||||
): MutableDeviceState<T> = MutableExternalState(scope, readInterval, initialValue, reader, writer)
|
|
@ -1,20 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
|
|
||||||
|
|
||||||
private class StateFlowAsState<T>(
|
|
||||||
val flow: MutableStateFlow<T>,
|
|
||||||
) : MutableDeviceState<T> {
|
|
||||||
override var value: T by flow::value
|
|
||||||
override val valueFlow: Flow<T> get() = flow
|
|
||||||
|
|
||||||
override fun toString(): String = "FlowAsState($value)"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a read-only [DeviceState] that wraps [MutableStateFlow].
|
|
||||||
* No data copy is performed.
|
|
||||||
*/
|
|
||||||
public fun <T> MutableStateFlow<T>.asDeviceState(): MutableDeviceState<T> = StateFlowAsState(this)
|
|
@ -1,53 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [MutableDeviceState] that does not correspond to a physical state
|
|
||||||
*
|
|
||||||
* @param callback a synchronous callback that could be used without a scope
|
|
||||||
*/
|
|
||||||
private class VirtualDeviceState<T>(
|
|
||||||
initialValue: T,
|
|
||||||
private val callback: (T) -> Unit = {}
|
|
||||||
) : MutableDeviceState<T> {
|
|
||||||
private val flow = MutableStateFlow(initialValue)
|
|
||||||
override val valueFlow: Flow<T> get() = flow
|
|
||||||
|
|
||||||
override var value: T
|
|
||||||
get() = flow.value
|
|
||||||
set(value) {
|
|
||||||
flow.value = value
|
|
||||||
callback(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "VirtualDeviceState($value)"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [MutableDeviceState] that does not correspond to a physical state
|
|
||||||
*
|
|
||||||
* @param callback a synchronous callback that could be used without a scope
|
|
||||||
*/
|
|
||||||
public fun <T> MutableDeviceState(
|
|
||||||
initialValue: T,
|
|
||||||
callback: (T) -> Unit = {}
|
|
||||||
): MutableDeviceState<T> = VirtualDeviceState(initialValue, callback)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [DeviceState] with constant value
|
|
||||||
*/
|
|
||||||
public fun <T> DeviceState(
|
|
||||||
value: T
|
|
||||||
): DeviceState<T> = object : DeviceState<T> {
|
|
||||||
override val value: T get() = value
|
|
||||||
override val valueFlow: Flow<T>
|
|
||||||
get() = emptyFlow()
|
|
||||||
|
|
||||||
override fun toString(): String = "ConstDeviceState($value)"
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.models
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.*
|
|
||||||
import space.kscience.controls.constructor.units.*
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A model for inertial movement. Both linear and angular
|
|
||||||
*/
|
|
||||||
public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
|
|
||||||
context: Context,
|
|
||||||
force: DeviceState<Double>, //TODO add system unit sets
|
|
||||||
inertia: Double,
|
|
||||||
public val position: MutableDeviceState<NumericalValue<U>>,
|
|
||||||
public val velocity: MutableDeviceState<NumericalValue<V>>,
|
|
||||||
) : ModelConstructor(context) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerState(position)
|
|
||||||
registerState(velocity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var currentForce = force.value
|
|
||||||
|
|
||||||
private val movement = onTimer(DefaultTimer.REALTIME) { prev, next ->
|
|
||||||
val dtSeconds = (next - prev).toDouble(DurationUnit.SECONDS)
|
|
||||||
|
|
||||||
// compute new value based on velocity and acceleration from the previous step
|
|
||||||
position.value += NumericalValue(velocity.value.value * dtSeconds + currentForce / inertia * dtSeconds.pow(2) / 2)
|
|
||||||
|
|
||||||
// compute new velocity based on acceleration on the previous step
|
|
||||||
velocity.value += NumericalValue(currentForce / inertia * dtSeconds)
|
|
||||||
currentForce = force.value
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
/**
|
|
||||||
* Linear inertial model with [force] in newtons and [mass] in kilograms
|
|
||||||
*/
|
|
||||||
public fun linear(
|
|
||||||
context: Context,
|
|
||||||
force: DeviceState<NumericalValue<Newtons>>,
|
|
||||||
mass: NumericalValue<Kilograms>,
|
|
||||||
position: MutableDeviceState<NumericalValue<Meters>>,
|
|
||||||
velocity: MutableDeviceState<NumericalValue<MetersPerSecond>> = MutableDeviceState(NumericalValue(0.0)),
|
|
||||||
): Inertia<Meters, MetersPerSecond> = Inertia(
|
|
||||||
context = context,
|
|
||||||
force = force.values(),
|
|
||||||
inertia = mass.value,
|
|
||||||
position = position,
|
|
||||||
velocity = velocity
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun circular(
|
|
||||||
context: Context,
|
|
||||||
force: DeviceState<NumericalValue<NewtonsMeters>>,
|
|
||||||
momentOfInertia: NumericalValue<KgM2>,
|
|
||||||
position: MutableDeviceState<NumericalValue<Degrees>>,
|
|
||||||
velocity: MutableDeviceState<NumericalValue<DegreesPerSecond>> = MutableDeviceState(NumericalValue(0.0)),
|
|
||||||
): Inertia<Degrees, DegreesPerSecond> = Inertia(
|
|
||||||
context = context,
|
|
||||||
force = force.values(),
|
|
||||||
inertia = momentOfInertia.value,
|
|
||||||
position = position,
|
|
||||||
velocity = velocity
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
31
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/models/Leadscrew.kt
31
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/models/Leadscrew.kt
@ -1,31 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.models
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.DeviceState
|
|
||||||
import space.kscience.controls.constructor.ModelConstructor
|
|
||||||
import space.kscience.controls.constructor.map
|
|
||||||
import space.kscience.controls.constructor.units.*
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import kotlin.math.PI
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://en.wikipedia.org/wiki/Leadscrew
|
|
||||||
*/
|
|
||||||
public class Leadscrew(
|
|
||||||
context: Context,
|
|
||||||
public val leverage: NumericalValue<Meters>,
|
|
||||||
) : ModelConstructor(context) {
|
|
||||||
|
|
||||||
public fun torqueToForce(
|
|
||||||
stateOfTorque: DeviceState<NumericalValue<NewtonsMeters>>,
|
|
||||||
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfTorque) { torque ->
|
|
||||||
NumericalValue(torque.value / leverage.value )
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun degreesToMeters(
|
|
||||||
stateOfAngle: DeviceState<NumericalValue<Degrees>>,
|
|
||||||
offset: NumericalValue<Meters> = NumericalValue(0),
|
|
||||||
): DeviceState<NumericalValue<Meters>> = DeviceState.map(stateOfAngle) { degrees ->
|
|
||||||
offset + NumericalValue(degrees.value * 2 * PI / 360 * leverage.value )
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.models
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.*
|
|
||||||
import space.kscience.controls.constructor.units.*
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 3D material point
|
|
||||||
*/
|
|
||||||
public class MaterialPoint(
|
|
||||||
context: Context,
|
|
||||||
force: DeviceState<XYZ<Newtons>>,
|
|
||||||
mass: NumericalValue<Kilograms>,
|
|
||||||
public val position: MutableDeviceState<XYZ<Meters>>,
|
|
||||||
public val velocity: MutableDeviceState<XYZ<MetersPerSecond>>,
|
|
||||||
) : ModelConstructor(context) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerState(position)
|
|
||||||
registerState(velocity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var currentForce = force.value
|
|
||||||
|
|
||||||
private val movement = onTimer(
|
|
||||||
DefaultTimer.REALTIME,
|
|
||||||
reads = setOf(velocity, position),
|
|
||||||
writes = setOf(velocity, position)
|
|
||||||
) { prev, next ->
|
|
||||||
val dtSeconds = (next - prev).toDouble(DurationUnit.SECONDS)
|
|
||||||
|
|
||||||
// compute new value based on velocity and acceleration from the previous step
|
|
||||||
val deltaR = (velocity.value * dtSeconds).cast(Meters) +
|
|
||||||
(currentForce / mass.value * dtSeconds.pow(2) / 2).cast(Meters)
|
|
||||||
position.value += deltaR
|
|
||||||
|
|
||||||
// compute new velocity based on acceleration on the previous step
|
|
||||||
val deltaV = (currentForce / mass.value * dtSeconds).cast(MetersPerSecond)
|
|
||||||
//TODO apply energy correction
|
|
||||||
//val work = deltaR.length.value * currentForce.length.value
|
|
||||||
velocity.value += deltaV
|
|
||||||
|
|
||||||
currentForce = force.value
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.models
|
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import space.kscience.controls.constructor.*
|
|
||||||
import space.kscience.controls.constructor.units.*
|
|
||||||
import space.kscience.controls.manager.clock
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pid regulator parameters
|
|
||||||
*/
|
|
||||||
public data class PidParameters(
|
|
||||||
val kp: Double,
|
|
||||||
val ki: Double,
|
|
||||||
val kd: Double,
|
|
||||||
val timeStep: Duration = 10.milliseconds,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A PID regulator
|
|
||||||
*
|
|
||||||
* @param P units of position values
|
|
||||||
* @param O units of output values
|
|
||||||
*/
|
|
||||||
public class PidRegulator<P : UnitsOfMeasurement, O : UnitsOfMeasurement>(
|
|
||||||
context: Context,
|
|
||||||
private val position: DeviceState<NumericalValue<P>>,
|
|
||||||
public var pidParameters: PidParameters, // TODO expose as property
|
|
||||||
output: MutableDeviceState<NumericalValue<O>> = MutableDeviceState(NumericalValue(0.0)),
|
|
||||||
private val convertOutput: (NumericalValue<P>) -> NumericalValue<O> = { NumericalValue(it.value) },
|
|
||||||
) : ModelConstructor(context) {
|
|
||||||
|
|
||||||
public val target: MutableDeviceState<NumericalValue<P>> = stateOf(NumericalValue(0.0))
|
|
||||||
public val output: MutableDeviceState<NumericalValue<O>> = registerState(output)
|
|
||||||
|
|
||||||
private val updateJob = launch {
|
|
||||||
var lastPosition: NumericalValue<P> = target.value
|
|
||||||
|
|
||||||
var integral: NumericalValue<P> = NumericalValue(0.0)
|
|
||||||
|
|
||||||
val mutex = Mutex()
|
|
||||||
|
|
||||||
val clock = context.clock
|
|
||||||
|
|
||||||
var lastTime = clock.now()
|
|
||||||
|
|
||||||
while (isActive) {
|
|
||||||
delay(pidParameters.timeStep)
|
|
||||||
mutex.withLock {
|
|
||||||
val realTime = clock.now()
|
|
||||||
val delta: NumericalValue<P> = target.value - position.value
|
|
||||||
val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS)
|
|
||||||
integral += delta * dtSeconds
|
|
||||||
val derivative = (position.value - lastPosition) / dtSeconds
|
|
||||||
|
|
||||||
//set last time and value to new values
|
|
||||||
lastTime = realTime
|
|
||||||
lastPosition = position.value
|
|
||||||
|
|
||||||
output.value =
|
|
||||||
convertOutput(pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
70
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/models/RangeState.kt
70
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/models/RangeState.kt
@ -1,70 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.models
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import space.kscience.controls.constructor.DeviceState
|
|
||||||
import space.kscience.controls.constructor.MutableDeviceState
|
|
||||||
import space.kscience.controls.constructor.map
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.UnitsOfMeasurement
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A state describing a [T] value in the [range]
|
|
||||||
*/
|
|
||||||
public open class RangeState<T : Comparable<T>>(
|
|
||||||
private val input: DeviceState<T>,
|
|
||||||
public val range: ClosedRange<T>,
|
|
||||||
) : DeviceState<T> {
|
|
||||||
|
|
||||||
override val valueFlow: Flow<T> get() = input.valueFlow.map {
|
|
||||||
it.coerceIn(range)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val value: T get() = input.value.coerceIn(range)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A state showing that the range is on its lower boundary
|
|
||||||
*/
|
|
||||||
public val atStart: DeviceState<Boolean> = input.map { it <= range.start }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A state showing that the range is on its higher boundary
|
|
||||||
*/
|
|
||||||
public val atEnd: DeviceState<Boolean> = input.map { it >= range.endInclusive }
|
|
||||||
|
|
||||||
override fun toString(): String = "DoubleRangeState(value=${value},range=$range)"
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MutableRangeState<T : Comparable<T>>(
|
|
||||||
private val mutableInput: MutableDeviceState<T>,
|
|
||||||
range: ClosedRange<T>,
|
|
||||||
) : RangeState<T>(mutableInput, range), MutableDeviceState<T> {
|
|
||||||
override var value: T
|
|
||||||
get() = super.value
|
|
||||||
set(value) {
|
|
||||||
mutableInput.value = value.coerceIn(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T : Comparable<T>> MutableRangeState(
|
|
||||||
initialValue: T,
|
|
||||||
range: ClosedRange<T>,
|
|
||||||
): MutableRangeState<T> = MutableRangeState<T>(MutableDeviceState(initialValue), range)
|
|
||||||
|
|
||||||
public fun <U : UnitsOfMeasurement> MutableRangeState(
|
|
||||||
initialValue: Double,
|
|
||||||
range: ClosedRange<Double>,
|
|
||||||
): MutableRangeState<NumericalValue<U>> = MutableRangeState(
|
|
||||||
initialValue = NumericalValue(initialValue),
|
|
||||||
range = NumericalValue<U>(range.start)..NumericalValue<U>(range.endInclusive)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Comparable<T>> DeviceState<T>.coerceIn(
|
|
||||||
range: ClosedRange<T>,
|
|
||||||
): RangeState<T> = RangeState(this, range)
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Comparable<T>> MutableDeviceState<T>.coerceIn(
|
|
||||||
range: ClosedRange<T>,
|
|
||||||
): MutableRangeState<T> = MutableRangeState(this, range)
|
|
@ -1,25 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.models
|
|
||||||
|
|
||||||
import space.kscience.controls.constructor.*
|
|
||||||
import space.kscience.controls.constructor.units.Degrees
|
|
||||||
import space.kscience.controls.constructor.units.NumericalValue
|
|
||||||
import space.kscience.controls.constructor.units.times
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A reducer device used for simulations only (no public properties)
|
|
||||||
*/
|
|
||||||
public class Reducer(
|
|
||||||
context: Context,
|
|
||||||
public val ratio: Double,
|
|
||||||
public val input: DeviceState<NumericalValue<Degrees>>,
|
|
||||||
public val output: MutableDeviceState<NumericalValue<Degrees>>,
|
|
||||||
) : ModelConstructor(context) {
|
|
||||||
init {
|
|
||||||
registerState(input)
|
|
||||||
registerState(output)
|
|
||||||
transformTo(input, output) {
|
|
||||||
it * ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.units
|
|
||||||
|
|
||||||
public enum class Direction(public val coef: Int) {
|
|
||||||
UP(1),
|
|
||||||
DOWN(-1)
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.units
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.meta.double
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A value without identity coupled to units of measurements.
|
|
||||||
*/
|
|
||||||
@JvmInline
|
|
||||||
public value class NumericalValue<U : UnitsOfMeasurement>(public val value: Double) : Comparable<NumericalValue<U>> {
|
|
||||||
override fun compareTo(other: NumericalValue<U>): Int = value.compareTo(other.value)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <U : UnitsOfMeasurement> NumericalValue(
|
|
||||||
number: Number,
|
|
||||||
): NumericalValue<U> = NumericalValue(number.toDouble())
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.plus(
|
|
||||||
other: NumericalValue<U>,
|
|
||||||
): NumericalValue<U> = NumericalValue(this.value + other.value)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.minus(
|
|
||||||
other: NumericalValue<U>,
|
|
||||||
): NumericalValue<U> = NumericalValue(this.value - other.value)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.times(
|
|
||||||
c: Number,
|
|
||||||
): NumericalValue<U> = NumericalValue(this.value * c.toDouble())
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> Number.times(
|
|
||||||
numericalValue: NumericalValue<U>,
|
|
||||||
): NumericalValue<U> = NumericalValue(numericalValue.value * toDouble())
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.times(
|
|
||||||
c: Double,
|
|
||||||
): NumericalValue<U> = NumericalValue(this.value * c)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.div(
|
|
||||||
c: Number,
|
|
||||||
): NumericalValue<U> = NumericalValue(this.value / c.toDouble())
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> NumericalValue<U>.div(other: NumericalValue<U>): Double =
|
|
||||||
value / other.value
|
|
||||||
|
|
||||||
public operator fun <U: UnitsOfMeasurement> NumericalValue<U>.unaryMinus(): NumericalValue<U> = NumericalValue(-value)
|
|
||||||
|
|
||||||
|
|
||||||
private object NumericalValueMetaConverter : MetaConverter<NumericalValue<*>> {
|
|
||||||
override fun convert(obj: NumericalValue<*>): Meta = Meta(obj.value)
|
|
||||||
|
|
||||||
override fun readOrNull(source: Meta): NumericalValue<*>? = source.double?.let { NumericalValue<Nothing>(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
public fun <U : UnitsOfMeasurement> MetaConverter.Companion.numerical(): MetaConverter<NumericalValue<U>> =
|
|
||||||
NumericalValueMetaConverter as MetaConverter<NumericalValue<U>>
|
|
@ -1,60 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.units
|
|
||||||
|
|
||||||
|
|
||||||
public interface UnitsOfMeasurement
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public interface UnitsOfLength : UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object Meters : UnitsOfLength
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public interface UnitsOfTime : UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object Seconds : UnitsOfTime
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public interface UnitsOfVelocity : UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object MetersPerSecond : UnitsOfVelocity
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public sealed interface UnitsOfAngles : UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object Radians : UnitsOfAngles
|
|
||||||
public data object Degrees : UnitsOfAngles
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public sealed interface UnitsAngularOfVelocity : UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object RadiansPerSecond : UnitsAngularOfVelocity
|
|
||||||
|
|
||||||
public data object DegreesPerSecond : UnitsAngularOfVelocity
|
|
||||||
|
|
||||||
/**/
|
|
||||||
public interface UnitsOfForce: UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object Newtons: UnitsOfForce
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public interface UnitsOfTorque: UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object NewtonsMeters: UnitsOfTorque
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public interface UnitsOfMass: UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object Kilograms : UnitsOfMass
|
|
||||||
|
|
||||||
/**/
|
|
||||||
|
|
||||||
public interface UnitsOfMomentOfInertia: UnitsOfMeasurement
|
|
||||||
|
|
||||||
public data object KgM2: UnitsOfMomentOfInertia
|
|
44
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/coordinates.kt
44
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/units/coordinates.kt
@ -1,44 +0,0 @@
|
|||||||
package space.kscience.controls.constructor.units
|
|
||||||
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
public data class XY<U : UnitsOfMeasurement>(val x: NumericalValue<U>, val y: NumericalValue<U>)
|
|
||||||
|
|
||||||
public fun <U : UnitsOfMeasurement> XY(x: Number, y: Number): XY<U> = XY(NumericalValue(x), NumericalValue((y)))
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XY<U>.plus(other: XY<U>): XY<U> =
|
|
||||||
XY(x + other.x, y + other.y)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XY<U>.times(c: Number): XY<U> = XY(x * c, y * c)
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XY<U>.div(c: Number): XY<U> = XY(x / c, y / c)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XY<U>.unaryMinus(): XY<U> = XY(-x, -y)
|
|
||||||
|
|
||||||
public data class XYZ<U : UnitsOfMeasurement>(
|
|
||||||
val x: NumericalValue<U>,
|
|
||||||
val y: NumericalValue<U>,
|
|
||||||
val z: NumericalValue<U>,
|
|
||||||
)
|
|
||||||
|
|
||||||
public val <U : UnitsOfMeasurement> XYZ<U>.length: NumericalValue<U>
|
|
||||||
get() = NumericalValue(
|
|
||||||
sqrt(x.value.pow(2) + y.value.pow(2) + z.value.pow(2))
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <U : UnitsOfMeasurement> XYZ(x: Number, y: Number, z: Number): XYZ<U> =
|
|
||||||
XYZ(NumericalValue(x), NumericalValue((y)), NumericalValue(z))
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER")
|
|
||||||
public fun <U : UnitsOfMeasurement, R : UnitsOfMeasurement> XYZ<U>.cast(units: R): XYZ<R> = this as XYZ<R>
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XYZ<U>.plus(other: XYZ<U>): XYZ<U> =
|
|
||||||
XYZ(x + other.x, y + other.y, z + other.z)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XYZ<U>.minus(other: XYZ<U>): XYZ<U> =
|
|
||||||
XYZ(x - other.x, y - other.y, z - other.z)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XYZ<U>.times(c: Number): XYZ<U> = XYZ(x * c, y * c, z * c)
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XYZ<U>.div(c: Number): XYZ<U> = XYZ(x / c, y / c, z / c)
|
|
||||||
|
|
||||||
public operator fun <U : UnitsOfMeasurement> XYZ<U>.unaryMinus(): XYZ<U> = XYZ(-x, -y, -z)
|
|
43
controls-constructor/src/commonTest/kotlin/space/kscience/controls/constructor/DeviceGroupTest.kt
43
controls-constructor/src/commonTest/kotlin/space/kscience/controls/constructor/DeviceGroupTest.kt
@ -1,43 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.DeviceLifeCycleMessage
|
|
||||||
import space.kscience.controls.api.LifecycleState
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.controls.manager.install
|
|
||||||
import space.kscience.controls.spec.doRecurring
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.context.Global
|
|
||||||
import space.kscience.dataforge.context.request
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
class DeviceGroupTest {
|
|
||||||
|
|
||||||
class TestDevice(context: Context) : DeviceConstructor(context) {
|
|
||||||
|
|
||||||
companion object : Factory<Device> {
|
|
||||||
override fun build(context: Context, meta: Meta): Device = TestDevice(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRecurringRead() = runTest {
|
|
||||||
var counter = 10
|
|
||||||
val testDevice = Global.request(DeviceManager).install("test", TestDevice)
|
|
||||||
testDevice.doRecurring(1.milliseconds) {
|
|
||||||
counter--
|
|
||||||
println(counter)
|
|
||||||
if (counter <= 0) {
|
|
||||||
testDevice.stop()
|
|
||||||
}
|
|
||||||
error("Error!")
|
|
||||||
}
|
|
||||||
testDevice.messageFlow.first { it is DeviceLifeCycleMessage && it.state == LifecycleState.STOPPED }
|
|
||||||
println("stopped")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package space.kscience.controls.constructor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.flow.take
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import space.kscience.controls.manager.ClockManager
|
|
||||||
import space.kscience.dataforge.context.Global
|
|
||||||
import space.kscience.dataforge.context.request
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
class TimerTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun timer() = runTest {
|
|
||||||
val timer = TimerState(Global.request(ClockManager), 10.milliseconds)
|
|
||||||
timer.valueFlow.take(100).onEach {
|
|
||||||
println(it)
|
|
||||||
}.collect()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
# 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.4.0-dev-7`.
|
|
||||||
|
|
||||||
**Gradle Kotlin DSL:**
|
|
||||||
```kotlin
|
|
||||||
repositories {
|
|
||||||
maven("https://repo.kotlin.link")
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("space.kscience:controls-core:0.4.0-dev-7")
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,915 +0,0 @@
|
|||||||
public final class space/kscience/controls/api/ActionDescriptor {
|
|
||||||
public static final field Companion Lspace/kscience/controls/api/ActionDescriptor$Companion;
|
|
||||||
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
|
||||||
public fun <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V
|
|
||||||
public synthetic fun <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> ()V
|
|
||||||
public synthetic fun <init> (ILspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
|
||||||
public fun <init> (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V
|
|
||||||
public synthetic fun <init> (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 <init> (ILspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
|
||||||
public fun <init> (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V
|
|
||||||
public synthetic fun <init> (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 <init> (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 <init> (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 <init> (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 <init> (ILjava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;ZZLkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
|
||||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;ZZ)V
|
|
||||||
public synthetic fun <init> (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 <init> (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 <init> (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V
|
|
||||||
public synthetic fun <init> (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 <init> (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 <init> (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 <init> (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 <init> ()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 <init> (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;)V
|
|
||||||
public synthetic fun <init> (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 <init> (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)V
|
|
||||||
public synthetic fun <init> (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 <init> ()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 <init> (Lspace/kscience/dataforge/context/Context;Lkotlin/jvm/functions/Function1;)V
|
|
||||||
public synthetic fun <init> (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 <init> ()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 <init> (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 <init> (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V
|
|
||||||
public synthetic fun <init> (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 <init> (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V
|
|
||||||
public synthetic fun <init> (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 <init> ()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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
|||||||
import space.kscience.gradle.Maturity
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("space.kscience.gradle.mpp")
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
description = """
|
|
||||||
Core interfaces for building a device server
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
kscience {
|
|
||||||
jvm()
|
|
||||||
js()
|
|
||||||
native()
|
|
||||||
wasm()
|
|
||||||
useCoroutines()
|
|
||||||
useSerialization{
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
useContextReceivers()
|
|
||||||
commonMain {
|
|
||||||
api(libs.dataforge.io)
|
|
||||||
api(spclibs.kotlinx.datetime)
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmTest{
|
|
||||||
implementation(spclibs.logback.classic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
feature("deviceMessage", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt"){
|
|
||||||
"""
|
|
||||||
Specification for messages used to communicate between Controls-kt devices.
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package space.kscience.controls.api
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic bidirectional asynchronous sender/receiver object
|
|
||||||
*/
|
|
||||||
public interface AsynchronousSocket<T> : WithLifeCycle {
|
|
||||||
/**
|
|
||||||
* Send an object to the socket
|
|
||||||
*/
|
|
||||||
public suspend fun send(data: T)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flow of objects received from socket
|
|
||||||
*/
|
|
||||||
public fun subscribe(): Flow<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect an input to this socket.
|
|
||||||
* Multiple inputs could be connected to the same [AsynchronousSocket].
|
|
||||||
*
|
|
||||||
* This method suspends indefinitely, so it should be started in a separate coroutine.
|
|
||||||
*/
|
|
||||||
public suspend fun <T> AsynchronousSocket<T>.sendFlow(flow: Flow<T>) {
|
|
||||||
flow.collect { send(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
|||||||
package space.kscience.controls.api
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
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.meta.get
|
|
||||||
import space.kscience.dataforge.meta.string
|
|
||||||
import space.kscience.dataforge.misc.DfType
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* General interface describing a managed Device.
|
|
||||||
* [Device] is a supervisor scope encompassing all operations on a device.
|
|
||||||
* When canceled, cancels all running processes.
|
|
||||||
*/
|
|
||||||
@DfType(DEVICE_TARGET)
|
|
||||||
public interface Device : ContextAware, WithLifeCycle, CoroutineScope {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial configuration meta for the device
|
|
||||||
*/
|
|
||||||
public val meta: Meta get() = Meta.EMPTY
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of supported property descriptors
|
|
||||||
*/
|
|
||||||
public val propertyDescriptors: Collection<PropertyDescriptor>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of supported action descriptors. Action is a request to the device that
|
|
||||||
* may or may not change the properties
|
|
||||||
*/
|
|
||||||
public val actionDescriptors: Collection<ActionDescriptor>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the physical state of property and update/push notifications if needed.
|
|
||||||
*/
|
|
||||||
public suspend fun readProperty(propertyName: String): Meta
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set property [value] for a property with name [propertyName].
|
|
||||||
* In rare cases could suspend if the [Device] supports command queue, and it is full at the moment.
|
|
||||||
*/
|
|
||||||
public suspend fun writeProperty(propertyName: String, value: Meta)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
|
|
||||||
* multiple times.
|
|
||||||
*/
|
|
||||||
public val messageFlow: Flow<DeviceMessage>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an action request and suspend caller while request is being processed.
|
|
||||||
* Could return null if request does not return a meaningful answer.
|
|
||||||
*/
|
|
||||||
public suspend fun execute(actionName: String, argument: Meta? = null): Meta?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the device. This function suspends until the device is finished initialization.
|
|
||||||
* Does nothing if the device is started or is starting
|
|
||||||
*/
|
|
||||||
override suspend fun start(): Unit = Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close and terminate the device. This function does not wait for the device to be closed.
|
|
||||||
*/
|
|
||||||
override suspend fun stop() {
|
|
||||||
coroutineContext[Job]?.cancel("The device is closed")
|
|
||||||
logger.info { "Device $this is closed" }
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public const val DEVICE_TARGET: String = "device"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inner id of a device. Not necessary corresponds to the name in the parent container
|
|
||||||
*/
|
|
||||||
public val Device.id: String get() = meta["id"].string ?: "device[${hashCode().toString(16)}]"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Device that caches properties values
|
|
||||||
*/
|
|
||||||
public interface CachingDevice : Device {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immediately (without waiting) get the cached (logical) state of property or return null if it is invalid
|
|
||||||
*/
|
|
||||||
public fun getProperty(propertyName: String): Meta?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidate property (set logical state to invalid).
|
|
||||||
*
|
|
||||||
* This message is suspended to provide lock-free local property changes (they require coroutine context).
|
|
||||||
*/
|
|
||||||
public suspend fun invalidate(propertyName: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the logical state of property or suspend to read the physical value.
|
|
||||||
*/
|
|
||||||
public suspend fun Device.getOrReadProperty(propertyName: String): Meta = if (this is CachingDevice) {
|
|
||||||
getProperty(propertyName) ?: readProperty(propertyName)
|
|
||||||
} else {
|
|
||||||
readProperty(propertyName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a snapshot of the device logical state
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public fun CachingDevice.getAllProperties(): Meta = Meta {
|
|
||||||
for (descriptor in propertyDescriptors) {
|
|
||||||
set(descriptor.name.parseAsName(), getProperty(descriptor.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe on property changes for the whole device
|
|
||||||
*/
|
|
||||||
public fun Device.onPropertyChange(
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
callback: suspend PropertyChangedMessage.() -> Unit,
|
|
||||||
): Job = messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(scope)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Flow] of property change messages for specific property.
|
|
||||||
*/
|
|
||||||
public fun Device.propertyMessageFlow(propertyName: String): Flow<PropertyChangedMessage> = messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == propertyName }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React on device lifecycle events
|
|
||||||
*/
|
|
||||||
public fun Device.onLifecycleEvent(
|
|
||||||
block: suspend (LifecycleState) -> Unit
|
|
||||||
): Job = messageFlow.filterIsInstance<DeviceLifeCycleMessage>().onEach {
|
|
||||||
block(it.state)
|
|
||||||
}.launchIn(this)
|
|
@ -1,62 +0,0 @@
|
|||||||
package space.kscience.controls.api
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.provider.Path
|
|
||||||
import space.kscience.dataforge.provider.Provider
|
|
||||||
import space.kscience.dataforge.provider.asPath
|
|
||||||
import space.kscience.dataforge.provider.plus
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A hub that could locate multiple devices and redirect actions to them
|
|
||||||
*/
|
|
||||||
public interface DeviceHub : Provider {
|
|
||||||
public val devices: Map<Name, Device>
|
|
||||||
|
|
||||||
override val defaultTarget: String get() = Device.DEVICE_TARGET
|
|
||||||
|
|
||||||
override val defaultChainTarget: String get() = Device.DEVICE_TARGET
|
|
||||||
|
|
||||||
override fun content(target: String): Map<Name, Any> = if (target == Device.DEVICE_TARGET) {
|
|
||||||
devices
|
|
||||||
} else {
|
|
||||||
emptyMap()
|
|
||||||
}
|
|
||||||
//TODO send message on device change
|
|
||||||
|
|
||||||
public companion object
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun DeviceHub(deviceMap: Map<Name, Device>): DeviceHub = object : DeviceHub {
|
|
||||||
override val devices: Map<Name, Device>
|
|
||||||
get() = deviceMap
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all devices, including sub-devices
|
|
||||||
*/
|
|
||||||
public fun DeviceHub.provideAllDevices(): Map<Path, Device> = buildMap {
|
|
||||||
fun putAll(prefix: Path, hub: DeviceHub) {
|
|
||||||
hub.devices.forEach {
|
|
||||||
put(prefix + it.key.asPath(), it.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
devices.forEach {
|
|
||||||
val name: Name = it.key
|
|
||||||
put(name.asPath(), it.value)
|
|
||||||
(it.value as? DeviceHub)?.let { hub ->
|
|
||||||
putAll(name.asPath(), hub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun DeviceHub.readProperty(deviceName: Name, propertyName: String): Meta =
|
|
||||||
(devices[deviceName] ?: error("Device with name $deviceName not found in $this")).readProperty(propertyName)
|
|
||||||
|
|
||||||
public suspend fun DeviceHub.writeProperty(deviceName: Name, propertyName: String, value: Meta) {
|
|
||||||
(devices[deviceName] ?: error("Device with name $deviceName not found in $this")).writeProperty(propertyName, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: Meta?): Meta? =
|
|
||||||
(devices[deviceName] ?: error("Device with name $deviceName not found in $this")).execute(command, argument)
|
|
@ -1,255 +0,0 @@
|
|||||||
@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
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
|
||||||
import space.kscience.dataforge.io.Envelope
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.toJson
|
|
||||||
import space.kscience.dataforge.meta.toMeta
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public sealed class DeviceMessage {
|
|
||||||
public abstract val sourceDevice: Name?
|
|
||||||
public abstract val targetDevice: Name?
|
|
||||||
public abstract val comment: String?
|
|
||||||
public abstract val time: Instant
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the source device name for composition. If the original name is null, the resulting name is also null.
|
|
||||||
*/
|
|
||||||
public abstract fun changeSource(block: (Name) -> Name): DeviceMessage
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public fun error(
|
|
||||||
cause: Throwable,
|
|
||||||
sourceDevice: Name,
|
|
||||||
targetDevice: Name? = null,
|
|
||||||
): DeviceErrorMessage = DeviceErrorMessage(
|
|
||||||
errorMessage = cause.message,
|
|
||||||
errorType = cause::class.simpleName,
|
|
||||||
errorStackTrace = cause.stackTraceToString(),
|
|
||||||
sourceDevice = sourceDevice,
|
|
||||||
targetDevice = targetDevice
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun fromMeta(meta: Meta): DeviceMessage = Json.decodeFromJsonElement(meta.toJson())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify that property is changed. [sourceDevice] is mandatory.
|
|
||||||
* [property] corresponds to property name.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("property.changed")
|
|
||||||
public data class PropertyChangedMessage(
|
|
||||||
public val property: String,
|
|
||||||
public val value: Meta,
|
|
||||||
override val sourceDevice: Name = Name.EMPTY,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A command to set or invalidate property. [targetDevice] is mandatory.
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("property.set")
|
|
||||||
public data class PropertySetMessage(
|
|
||||||
public val property: String,
|
|
||||||
public val value: Meta,
|
|
||||||
override val sourceDevice: Name? = null,
|
|
||||||
override val targetDevice: Name?,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A command to request property value asynchronously. [targetDevice] is mandatory.
|
|
||||||
* The property value should be returned asynchronously via [PropertyChangedMessage].
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("property.get")
|
|
||||||
public data class PropertyGetMessage(
|
|
||||||
public val property: String,
|
|
||||||
override val sourceDevice: Name? = null,
|
|
||||||
override val targetDevice: Name,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request device description. The result is returned in form of [DescriptionMessage]
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("description.get")
|
|
||||||
public data class GetDescriptionMessage(
|
|
||||||
override val sourceDevice: Name? = null,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The full device description message
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("description")
|
|
||||||
public data class DescriptionMessage(
|
|
||||||
val description: Meta,
|
|
||||||
val properties: Collection<PropertyDescriptor>,
|
|
||||||
val actions: Collection<ActionDescriptor>,
|
|
||||||
override val sourceDevice: Name,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A request to execute an action. [targetDevice] is mandatory
|
|
||||||
*
|
|
||||||
* @param requestId action request id that should be returned in a response
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("action.execute")
|
|
||||||
public data class ActionExecuteMessage(
|
|
||||||
public val action: String,
|
|
||||||
public val argument: Meta?,
|
|
||||||
public val requestId: String,
|
|
||||||
override val sourceDevice: Name? = null,
|
|
||||||
override val targetDevice: Name,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronous action result. [sourceDevice] is mandatory
|
|
||||||
*
|
|
||||||
* @param requestId request id passed in the request
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("action.result")
|
|
||||||
public data class ActionResultMessage(
|
|
||||||
public val action: String,
|
|
||||||
public val result: Meta?,
|
|
||||||
public val requestId: String,
|
|
||||||
override val sourceDevice: Name,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies listeners that a new binary with given [contentId] and [contentMeta] is available.
|
|
||||||
*
|
|
||||||
* [contentMeta] includes public information that could be shared with loop subscribers. It should not contain sensitive data.
|
|
||||||
*
|
|
||||||
* The binary itself could not be provided via [DeviceMessage] API.
|
|
||||||
* [space.kscience.controls.peer.PeerConnection] must be used instead
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("binary.notification")
|
|
||||||
public data class BinaryNotificationMessage(
|
|
||||||
val contentId: String,
|
|
||||||
val contentMeta: Meta,
|
|
||||||
override val sourceDevice: Name,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message states that the message is received, but no meaningful response is produced.
|
|
||||||
* This message could be used for a heartbeat.
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("empty")
|
|
||||||
public data class EmptyDeviceMessage(
|
|
||||||
override val sourceDevice: Name? = null,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information log message
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("log")
|
|
||||||
public data class DeviceLogMessage(
|
|
||||||
val message: String,
|
|
||||||
val data: Meta? = null,
|
|
||||||
override val sourceDevice: Name = Name.EMPTY,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The evaluation of the message produced a service error
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("error")
|
|
||||||
public data class DeviceErrorMessage(
|
|
||||||
public val errorMessage: String?,
|
|
||||||
public val errorType: String? = null,
|
|
||||||
public val errorStackTrace: String? = null,
|
|
||||||
override val sourceDevice: Name = Name.EMPTY,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Device [Device.lifecycleState] is changed
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("lifecycle")
|
|
||||||
public data class DeviceLifeCycleMessage(
|
|
||||||
val state: LifecycleState,
|
|
||||||
override val sourceDevice: Name = Name.EMPTY,
|
|
||||||
override val targetDevice: Name? = null,
|
|
||||||
override val comment: String? = null,
|
|
||||||
@EncodeDefault override val time: Instant = Clock.System.now(),
|
|
||||||
) : DeviceMessage() {
|
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public fun DeviceMessage.toMeta(): Meta = Json.encodeToJsonElement(this).toMeta()
|
|
||||||
|
|
||||||
public fun DeviceMessage.toEnvelope(): Envelope = Envelope(toMeta(), null)
|
|
@ -1,59 +0,0 @@
|
|||||||
package space.kscience.controls.api
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A lifecycle state of a device
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
public enum class LifecycleState {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Device is initializing
|
|
||||||
*/
|
|
||||||
STARTING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Device is initialized and running
|
|
||||||
*/
|
|
||||||
STARTED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Device is closed
|
|
||||||
*/
|
|
||||||
STOPPED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The device encountered irrecoverable error
|
|
||||||
*/
|
|
||||||
ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object that could be started or stopped functioning
|
|
||||||
*/
|
|
||||||
public interface WithLifeCycle {
|
|
||||||
|
|
||||||
public suspend fun start()
|
|
||||||
|
|
||||||
public suspend fun stop()
|
|
||||||
|
|
||||||
public val lifecycleState: LifecycleState
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind this object lifecycle to a device lifecycle
|
|
||||||
*
|
|
||||||
* The starting and stopping are done in device scope
|
|
||||||
*/
|
|
||||||
public fun WithLifeCycle.bindToDeviceLifecycle(device: Device){
|
|
||||||
device.onLifecycleEvent {
|
|
||||||
when(it){
|
|
||||||
LifecycleState.STARTING -> start()
|
|
||||||
LifecycleState.STARTED -> {/*ignore*/}
|
|
||||||
LifecycleState.STOPPED -> stop()
|
|
||||||
LifecycleState.ERROR -> stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package space.kscience.controls.api
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
|
|
||||||
|
|
||||||
//TODO add proper builders
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A descriptor for property
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
public class PropertyDescriptor(
|
|
||||||
public val name: String,
|
|
||||||
public var description: String? = null,
|
|
||||||
public var metaDescriptor: MetaDescriptor = MetaDescriptor(),
|
|
||||||
public var readable: Boolean = true,
|
|
||||||
public var mutable: Boolean = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.() -> Unit) {
|
|
||||||
metaDescriptor = MetaDescriptor {
|
|
||||||
from(metaDescriptor)
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A descriptor for property
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
public class ActionDescriptor(
|
|
||||||
public val name: String,
|
|
||||||
public var description: String? = null,
|
|
||||||
public var inputMetaDescriptor: MetaDescriptor = MetaDescriptor(),
|
|
||||||
public var outputMetaDescriptor: MetaDescriptor = MetaDescriptor()
|
|
||||||
)
|
|
@ -1,109 +0,0 @@
|
|||||||
package space.kscience.controls.manager
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.dataforge.context.*
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.double
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.math.roundToLong
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
@OptIn(InternalCoroutinesApi::class)
|
|
||||||
private class CompressedTimeDispatcher(
|
|
||||||
val clockManager: ClockManager,
|
|
||||||
val dispatcher: CoroutineDispatcher,
|
|
||||||
val compression: Double,
|
|
||||||
) : CoroutineDispatcher(), Delay {
|
|
||||||
|
|
||||||
@InternalCoroutinesApi
|
|
||||||
override fun dispatchYield(context: CoroutineContext, block: Runnable) {
|
|
||||||
dispatcher.dispatchYield(context, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isDispatchNeeded(context: CoroutineContext): Boolean = dispatcher.isDispatchNeeded(context)
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
|
||||||
override fun limitedParallelism(parallelism: Int): CoroutineDispatcher = dispatcher.limitedParallelism(parallelism)
|
|
||||||
|
|
||||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
|
||||||
dispatcher.dispatch(context, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val delay = ((dispatcher as? Delay) ?: (Dispatchers.Default as Delay))
|
|
||||||
|
|
||||||
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
|
|
||||||
delay.scheduleResumeAfterDelay((timeMillis / compression).roundToLong(), continuation)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
|
|
||||||
return delay.invokeOnTimeout((timeMillis / compression).roundToLong(), block, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CompressedClock(
|
|
||||||
val start: Instant,
|
|
||||||
val compression: Double,
|
|
||||||
val baseClock: Clock = Clock.System,
|
|
||||||
) : Clock {
|
|
||||||
override fun now(): Instant {
|
|
||||||
val elapsed = (baseClock.now() - start)
|
|
||||||
return start + elapsed / compression
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ClockManager : AbstractPlugin() {
|
|
||||||
override val tag: PluginTag get() = Companion.tag
|
|
||||||
|
|
||||||
public val timeCompression: Double by meta.double(1.0)
|
|
||||||
|
|
||||||
public val clock: Clock by lazy {
|
|
||||||
if (timeCompression == 1.0) {
|
|
||||||
Clock.System
|
|
||||||
} else {
|
|
||||||
CompressedClock(Clock.System.now(), timeCompression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a [CoroutineDispatcher] with compressed time based on given [dispatcher]
|
|
||||||
*/
|
|
||||||
public fun asDispatcher(
|
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
|
||||||
): CoroutineDispatcher = if (timeCompression == 1.0) {
|
|
||||||
dispatcher
|
|
||||||
} else {
|
|
||||||
CompressedTimeDispatcher(this, dispatcher, timeCompression)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun scheduleWithFixedDelay(tick: Duration, block: suspend () -> Unit): Job = context.launch(asDispatcher()) {
|
|
||||||
while (isActive) {
|
|
||||||
delay(tick)
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public companion object : PluginFactory<ClockManager> {
|
|
||||||
override val tag: PluginTag = PluginTag("clock", group = PluginTag.DATAFORGE_GROUP)
|
|
||||||
|
|
||||||
override fun build(context: Context, meta: Meta): ClockManager = ClockManager()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public val Context.clock: Clock get() = plugins[ClockManager]?.clock ?: Clock.System
|
|
||||||
|
|
||||||
public val Device.clock: Clock get() = context.clock
|
|
||||||
|
|
||||||
public fun Device.getCoroutineDispatcher(dispatcher: CoroutineDispatcher = Dispatchers.Default): CoroutineDispatcher =
|
|
||||||
context.plugins[ClockManager]?.asDispatcher(dispatcher) ?: dispatcher
|
|
||||||
|
|
||||||
public fun ContextBuilder.withTimeCompression(compression: Double) {
|
|
||||||
require(compression > 0.0) { "Time compression must be greater than zero." }
|
|
||||||
plugin(ClockManager) {
|
|
||||||
"timeCompression" put compression
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package space.kscience.controls.manager
|
|
||||||
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.DeviceHub
|
|
||||||
import space.kscience.controls.api.id
|
|
||||||
import space.kscience.dataforge.context.*
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MutableMeta
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.get
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
import kotlin.collections.set
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DataForge Context plugin that allows to manage devices locally
|
|
||||||
*/
|
|
||||||
public class DeviceManager : AbstractPlugin(), DeviceHub {
|
|
||||||
override val tag: PluginTag get() = Companion.tag
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actual list of connected devices
|
|
||||||
*/
|
|
||||||
private val _devices = HashMap<Name, Device>()
|
|
||||||
override val devices: Map<Name, Device> get() = _devices
|
|
||||||
|
|
||||||
public fun registerDevice(name: Name, device: Device) {
|
|
||||||
_devices[name] = device
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun content(target: String): Map<Name, Any> = super<DeviceHub>.content(target)
|
|
||||||
|
|
||||||
public companion object : PluginFactory<DeviceManager> {
|
|
||||||
override val tag: PluginTag = PluginTag("devices", group = PluginTag.DATAFORGE_GROUP)
|
|
||||||
|
|
||||||
override fun build(context: Context, meta: Meta): DeviceManager = DeviceManager()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceManager.install(name: String, device: D): D {
|
|
||||||
registerDevice(name.parseAsName(), device)
|
|
||||||
device.launch {
|
|
||||||
device.start()
|
|
||||||
}
|
|
||||||
return device
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceManager.install(device: D): D = install(device.id, device)
|
|
||||||
|
|
||||||
|
|
||||||
public fun <D : Device> Context.install(name: String, device: D): D = request(DeviceManager).install(name, device)
|
|
||||||
|
|
||||||
public fun <D : Device> Context.install(device: D): D = request(DeviceManager).install(device.id, device)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register and start a device built by [factory] with current [Context] and [meta].
|
|
||||||
*/
|
|
||||||
public fun <D : Device> DeviceManager.install(name: String, factory: Factory<D>, meta: Meta = Meta.EMPTY): D =
|
|
||||||
install(name, factory(meta, context))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A delegate that initializes device on the first use
|
|
||||||
*/
|
|
||||||
public inline fun <D : Device> DeviceManager.installing(
|
|
||||||
factory: Factory<D>,
|
|
||||||
builder: MutableMeta.() -> Unit = {},
|
|
||||||
): ReadOnlyProperty<Any?, D> {
|
|
||||||
val meta = Meta(builder)
|
|
||||||
return ReadOnlyProperty { _, property ->
|
|
||||||
val name = property.name
|
|
||||||
val current = devices[name]
|
|
||||||
if (current == null) {
|
|
||||||
install(name, factory, meta)
|
|
||||||
} else if (current.meta != meta) {
|
|
||||||
error("Meta mismatch. Current device meta: ${current.meta}, but factory meta is $meta")
|
|
||||||
} else {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
current as D
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package space.kscience.controls.manager
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.merge
|
|
||||||
import space.kscience.controls.api.*
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.plus
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a message targeted at this [Device], assuming its name is [deviceTarget].
|
|
||||||
*/
|
|
||||||
public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMessage): DeviceMessage? = try {
|
|
||||||
when (request) {
|
|
||||||
is PropertyGetMessage -> {
|
|
||||||
PropertyChangedMessage(
|
|
||||||
property = request.property,
|
|
||||||
value = getOrReadProperty(request.property),
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is PropertySetMessage -> {
|
|
||||||
writeProperty(request.property, request.value)
|
|
||||||
PropertyChangedMessage(
|
|
||||||
property = request.property,
|
|
||||||
value = getOrReadProperty(request.property),
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ActionExecuteMessage -> {
|
|
||||||
ActionResultMessage(
|
|
||||||
action = request.action,
|
|
||||||
result = execute(request.action, request.argument),
|
|
||||||
requestId = request.requestId,
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is GetDescriptionMessage -> {
|
|
||||||
DescriptionMessage(
|
|
||||||
description = meta,
|
|
||||||
properties = propertyDescriptors,
|
|
||||||
actions = actionDescriptors,
|
|
||||||
sourceDevice = deviceTarget,
|
|
||||||
targetDevice = request.sourceDevice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is DescriptionMessage,
|
|
||||||
is PropertyChangedMessage,
|
|
||||||
is ActionResultMessage,
|
|
||||||
is BinaryNotificationMessage,
|
|
||||||
is DeviceErrorMessage,
|
|
||||||
is EmptyDeviceMessage,
|
|
||||||
is DeviceLogMessage,
|
|
||||||
is DeviceLifeCycleMessage,
|
|
||||||
-> null
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process incoming [DeviceMessage], using hub naming to find target.
|
|
||||||
* If the `targetDevice` is `null`, then the message is sent to each device in this hub
|
|
||||||
*/
|
|
||||||
public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): List<DeviceMessage> {
|
|
||||||
return try {
|
|
||||||
val targetName = request.targetDevice
|
|
||||||
if (targetName == null) {
|
|
||||||
devices.mapNotNull {
|
|
||||||
it.value.respondMessage(it.key, request)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val device = devices[targetName] ?: error("The device with name $targetName not found in $this")
|
|
||||||
listOfNotNull(device.respondMessage(targetName, request))
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
listOf(DeviceMessage.error(ex, sourceDevice = Name.EMPTY, targetDevice = request.sourceDevice))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect all messages from given [DeviceHub], applying proper relative names.
|
|
||||||
*/
|
|
||||||
public fun DeviceHub.hubMessageFlow(): Flow<DeviceMessage> {
|
|
||||||
|
|
||||||
val deviceMessageFlow = if (this is Device) messageFlow else emptyFlow()
|
|
||||||
|
|
||||||
val childrenFlows = devices.map { (token, childDevice) ->
|
|
||||||
if (childDevice is DeviceHub) {
|
|
||||||
childDevice.hubMessageFlow()
|
|
||||||
} else {
|
|
||||||
childDevice.messageFlow
|
|
||||||
}.map { deviceMessage ->
|
|
||||||
deviceMessage.changeSource { token + it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return merge(deviceMessageFlow, *childrenFlows.toTypedArray())
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package space.kscience.controls.misc
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.DeviceMessage
|
|
||||||
import space.kscience.controls.api.PropertyChangedMessage
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.name
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for device property history.
|
|
||||||
*/
|
|
||||||
public interface PropertyHistory<T> {
|
|
||||||
/**
|
|
||||||
* Flow property values filtered by a time range. The implementation could flow it as a chunk or provide paging.
|
|
||||||
* So the resulting flow is allowed to suspend.
|
|
||||||
*
|
|
||||||
* If [until] is in the future, the resulting flow is potentially unlimited.
|
|
||||||
* Theoretically, it could be also unlimited if the event source keeps producing new event with timestamp in a given range.
|
|
||||||
*/
|
|
||||||
public fun flowHistory(
|
|
||||||
from: Instant = Instant.DISTANT_PAST,
|
|
||||||
until: Instant = Clock.System.now(),
|
|
||||||
): Flow<ValueWithTime<T>>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An in-memory property values history collector
|
|
||||||
*/
|
|
||||||
public class CollectedPropertyHistory<T>(
|
|
||||||
public val scope: CoroutineScope,
|
|
||||||
eventFlow: Flow<DeviceMessage>,
|
|
||||||
public val deviceName: Name,
|
|
||||||
public val propertyName: String,
|
|
||||||
public val converter: MetaConverter<T>,
|
|
||||||
maxSize: Int = 1000,
|
|
||||||
) : PropertyHistory<T> {
|
|
||||||
|
|
||||||
private val store: SharedFlow<ValueWithTime<T>> = eventFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.sourceDevice == deviceName && it.property == propertyName }
|
|
||||||
.map { ValueWithTime(converter.read(it.value), it.time) }
|
|
||||||
.shareIn(scope, started = SharingStarted.Eagerly, replay = maxSize)
|
|
||||||
|
|
||||||
override fun flowHistory(from: Instant, until: Instant): Flow<ValueWithTime<T>> =
|
|
||||||
store.filter { it.time in from..until }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect and store in memory device property changes for a given property
|
|
||||||
*/
|
|
||||||
public fun <T> Device.collectPropertyHistory(
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
deviceName: Name,
|
|
||||||
propertyName: String,
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
maxSize: Int = 1000,
|
|
||||||
): PropertyHistory<T> = CollectedPropertyHistory(scope, messageFlow, deviceName, propertyName, converter, maxSize)
|
|
||||||
|
|
||||||
public fun <D : Device, T> D.collectPropertyHistory(
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
deviceName: Name,
|
|
||||||
spec: DevicePropertySpec<D, T>,
|
|
||||||
maxSize: Int = 1000,
|
|
||||||
): PropertyHistory<T> = collectPropertyHistory(scope, deviceName, spec.name, spec.converter, maxSize)
|
|
@ -1,69 +0,0 @@
|
|||||||
package space.kscience.controls.misc
|
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.io.Sink
|
|
||||||
import kotlinx.io.Source
|
|
||||||
import space.kscience.dataforge.io.IOFormat
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.meta.get
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A value coupled to a time it was obtained at
|
|
||||||
*/
|
|
||||||
public data class ValueWithTime<T>(val value: T, val time: Instant) {
|
|
||||||
public companion object {
|
|
||||||
/**
|
|
||||||
* Create a [ValueWithTime] format for given value value [IOFormat]
|
|
||||||
*/
|
|
||||||
public fun <T> ioFormat(
|
|
||||||
valueFormat: IOFormat<T>,
|
|
||||||
): IOFormat<ValueWithTime<T>> = ValueWithTimeIOFormat(valueFormat)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [MetaConverter] with time for given value [MetaConverter]
|
|
||||||
*/
|
|
||||||
public fun <T> metaConverter(
|
|
||||||
valueConverter: MetaConverter<T>,
|
|
||||||
): MetaConverter<ValueWithTime<T>> = ValueWithTimeMetaConverter(valueConverter)
|
|
||||||
|
|
||||||
|
|
||||||
public const val META_TIME_KEY: String = "time"
|
|
||||||
public const val META_VALUE_KEY: String = "value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ValueWithTimeIOFormat<T>(val valueFormat: IOFormat<T>) : IOFormat<ValueWithTime<T>> {
|
|
||||||
|
|
||||||
override fun readFrom(source: Source): ValueWithTime<T> {
|
|
||||||
val timestamp = InstantIOFormat.readFrom(source)
|
|
||||||
val value = valueFormat.readFrom(source)
|
|
||||||
return ValueWithTime(value, timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeTo(sink: Sink, obj: ValueWithTime<T>) {
|
|
||||||
InstantIOFormat.writeTo(sink, obj.time)
|
|
||||||
valueFormat.writeTo(sink, obj.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ValueWithTimeMetaConverter<T>(
|
|
||||||
val valueConverter: MetaConverter<T>,
|
|
||||||
) : MetaConverter<ValueWithTime<T>> {
|
|
||||||
|
|
||||||
|
|
||||||
override fun readOrNull(
|
|
||||||
source: Meta,
|
|
||||||
): ValueWithTime<T>? = valueConverter.read(source[ValueWithTime.META_VALUE_KEY] ?: Meta.EMPTY)?.let {
|
|
||||||
ValueWithTime(it, source[ValueWithTime.META_TIME_KEY]?.instant ?: Instant.DISTANT_PAST)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(obj: ValueWithTime<T>): Meta = Meta {
|
|
||||||
ValueWithTime.META_TIME_KEY put obj.time.toMeta()
|
|
||||||
ValueWithTime.META_VALUE_KEY put valueConverter.convert(obj.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Any> MetaConverter<T>.withTime(): MetaConverter<ValueWithTime<T>> = ValueWithTimeMetaConverter(this)
|
|
@ -1,62 +0,0 @@
|
|||||||
package space.kscience.controls.misc
|
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import space.kscience.dataforge.meta.*
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
import kotlin.time.toDuration
|
|
||||||
|
|
||||||
public fun Double.asMeta(): Meta = Meta(asValue())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a nullable [MetaConverter] from non-nullable one
|
|
||||||
*/
|
|
||||||
public fun <T : Any> MetaConverter<T>.nullable(): MetaConverter<T?> = object : MetaConverter<T?> {
|
|
||||||
override fun convert(obj: T?): Meta = obj?.let { this@nullable.convert(it) } ?: Meta(Null)
|
|
||||||
|
|
||||||
override fun readOrNull(source: Meta): T? = if (source.value == Null) null else this@nullable.readOrNull(source)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO to be moved to DF
|
|
||||||
private object DurationConverter : MetaConverter<Duration> {
|
|
||||||
override fun readOrNull(source: Meta): Duration = source.value?.double?.toDuration(DurationUnit.SECONDS)
|
|
||||||
?: run {
|
|
||||||
val unit: DurationUnit = source["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
|
|
||||||
val value = source[Meta.VALUE_KEY].double ?: error("No value present for Duration")
|
|
||||||
return@run value.toDuration(unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(obj: Duration): Meta = obj.toDouble(DurationUnit.SECONDS).asMeta()
|
|
||||||
}
|
|
||||||
|
|
||||||
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter
|
|
||||||
|
|
||||||
|
|
||||||
private object InstantConverter : MetaConverter<Instant> {
|
|
||||||
override fun readOrNull(source: Meta): Instant? = source.string?.let { Instant.parse(it) }
|
|
||||||
override fun convert(obj: Instant): Meta = Meta(obj.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
public val MetaConverter.Companion.instant: MetaConverter<Instant> get() = InstantConverter
|
|
||||||
|
|
||||||
private object DoubleRangeConverter : MetaConverter<ClosedFloatingPointRange<Double>> {
|
|
||||||
override fun readOrNull(source: Meta): ClosedFloatingPointRange<Double>? =
|
|
||||||
source.value?.doubleArray?.let { (start, end) ->
|
|
||||||
start..end
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(
|
|
||||||
obj: ClosedFloatingPointRange<Double>,
|
|
||||||
): Meta = Meta(doubleArrayOf(obj.start, obj.endInclusive).asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public val MetaConverter.Companion.doubleRange: MetaConverter<ClosedFloatingPointRange<Double>> get() = DoubleRangeConverter
|
|
||||||
|
|
||||||
private object StringListConverter : MetaConverter<List<String>> {
|
|
||||||
override fun convert(obj: List<String>): Meta = Meta(obj.map { it.asValue() }.asValue())
|
|
||||||
|
|
||||||
override fun readOrNull(source: Meta): List<String>? = source.stringList ?: source["@jsonArray"]?.stringList
|
|
||||||
}
|
|
||||||
|
|
||||||
public val MetaConverter.Companion.stringList: MetaConverter<List<String>> get() = StringListConverter
|
|
@ -1,42 +0,0 @@
|
|||||||
package space.kscience.controls.misc
|
|
||||||
|
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.io.Sink
|
|
||||||
import kotlinx.io.Source
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.io.IOFormat
|
|
||||||
import space.kscience.dataforge.io.IOFormatFactory
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.string
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlin.reflect.typeOf
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An [IOFormat] for [Instant]
|
|
||||||
*/
|
|
||||||
public object InstantIOFormat : IOFormat<Instant>, IOFormatFactory<Instant> {
|
|
||||||
override fun build(context: Context, meta: Meta): IOFormat<Instant> = this
|
|
||||||
|
|
||||||
override val name: Name = "instant".asName()
|
|
||||||
|
|
||||||
override val type: KType get() = typeOf<Instant>()
|
|
||||||
|
|
||||||
override fun writeTo(sink: Sink, obj: Instant) {
|
|
||||||
sink.writeLong(obj.epochSeconds)
|
|
||||||
sink.writeInt(obj.nanosecondsOfSecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readFrom(source: Source): Instant {
|
|
||||||
val seconds = source.readLong()
|
|
||||||
val nanoseconds = source.readInt()
|
|
||||||
return Instant.fromEpochSeconds(seconds, nanoseconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Instant.toMeta(): Meta = Meta(toString())
|
|
||||||
|
|
||||||
public val Meta.instant: Instant? get() = value?.string?.let { Instant.parse(it) }
|
|
@ -1,39 +0,0 @@
|
|||||||
package space.kscience.controls.peer
|
|
||||||
|
|
||||||
import space.kscience.dataforge.io.Envelope
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A manager that allows direct synchronous sending and receiving binary data
|
|
||||||
*/
|
|
||||||
public interface PeerConnection {
|
|
||||||
/**
|
|
||||||
* Receive an [Envelope] from a device on a given [address] with given [contentId].
|
|
||||||
*
|
|
||||||
* The address depends on the specifics of given [PeerConnection]. For example, it could be a TCP/IP port or
|
|
||||||
* magix endpoint name.
|
|
||||||
*
|
|
||||||
* Depending on [PeerConnection] implementation, the resulting [Envelope] could be lazy loaded
|
|
||||||
*
|
|
||||||
* Additional metadata in [requestMeta] could be required for authentication.
|
|
||||||
*/
|
|
||||||
public suspend fun receive(
|
|
||||||
address: String,
|
|
||||||
contentId: String,
|
|
||||||
requestMeta: Meta = Meta.EMPTY,
|
|
||||||
): Envelope?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an [envelope] to a device on a given [address]
|
|
||||||
*
|
|
||||||
* The address depends on the specifics of given [PeerConnection]. For example, it could be a TCP/IP port or
|
|
||||||
* magix endpoint name.
|
|
||||||
*
|
|
||||||
* Additional metadata in [requestMeta] could be required for authentication.
|
|
||||||
*/
|
|
||||||
public suspend fun send(
|
|
||||||
address: String,
|
|
||||||
envelope: Envelope,
|
|
||||||
requestMeta: Meta = Meta.EMPTY,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
|
||||||
import kotlinx.io.Source
|
|
||||||
import space.kscience.controls.api.AsynchronousSocket
|
|
||||||
import space.kscience.controls.api.LifecycleState
|
|
||||||
import space.kscience.dataforge.context.*
|
|
||||||
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 kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raw [ByteArray] port
|
|
||||||
*/
|
|
||||||
public interface AsynchronousPort : ContextAware, AsynchronousSocket<ByteArray>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Capture [AsynchronousPort] output as kotlinx-io [Source].
|
|
||||||
* [scope] controls the consummation.
|
|
||||||
* If the scope is canceled, the source stops producing.
|
|
||||||
*/
|
|
||||||
public fun AsynchronousPort.receiveAsSource(scope: CoroutineScope): Source = subscribe().consumeAsSource(scope)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common abstraction for [AsynchronousPort] based on [Channel]
|
|
||||||
*/
|
|
||||||
public abstract class AbstractAsynchronousPort(
|
|
||||||
override val context: Context,
|
|
||||||
public val meta: Meta,
|
|
||||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
|
||||||
) : AsynchronousPort {
|
|
||||||
|
|
||||||
|
|
||||||
protected val scope: CoroutineScope by lazy {
|
|
||||||
CoroutineScope(
|
|
||||||
coroutineContext +
|
|
||||||
SupervisorJob(coroutineContext[Job]) +
|
|
||||||
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) { "Asynchronous port error: " + throwable.stackTraceToString() } } +
|
|
||||||
CoroutineName(toString())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val outgoing = Channel<ByteArray>(meta["outgoing.capacity"].int ?: 100)
|
|
||||||
private val incoming = Channel<ByteArray>(meta["incoming.capacity"].int ?: 100)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method to synchronously send data
|
|
||||||
*/
|
|
||||||
protected abstract suspend fun write(data: ByteArray)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method to receive data synchronously
|
|
||||||
*/
|
|
||||||
protected suspend fun receive(data: ByteArray) {
|
|
||||||
logger.debug { "$this RECEIVED: ${data.decodeToString()}" }
|
|
||||||
incoming.send(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var sendJob: Job? = null
|
|
||||||
|
|
||||||
protected abstract fun onOpen()
|
|
||||||
|
|
||||||
final override suspend fun start() {
|
|
||||||
if (lifecycleState == LifecycleState.STOPPED) {
|
|
||||||
sendJob = scope.launch {
|
|
||||||
for (data in outgoing) {
|
|
||||||
try {
|
|
||||||
write(data)
|
|
||||||
logger.debug { "${this@AbstractAsynchronousPort} SENT: ${data.decodeToString()}" }
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
if (ex is CancellationException) throw ex
|
|
||||||
logger.error(ex) { "Error while writing data to the port" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onOpen()
|
|
||||||
} else {
|
|
||||||
logger.warn { "$this already started" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a data packet via the port
|
|
||||||
*/
|
|
||||||
override suspend fun send(data: ByteArray) {
|
|
||||||
check(lifecycleState == LifecycleState.STARTED) { "The port is not opened" }
|
|
||||||
outgoing.send(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raw flow of incoming data chunks. The chunks are not guaranteed to be complete phrases.
|
|
||||||
* To form phrases, some condition should be used on top of it.
|
|
||||||
* For example [stringsDelimitedIncoming] generates phrases with fixed delimiter.
|
|
||||||
*/
|
|
||||||
override fun subscribe(): Flow<ByteArray> = incoming.receiveAsFlow()
|
|
||||||
|
|
||||||
override suspend fun stop() {
|
|
||||||
outgoing.close()
|
|
||||||
incoming.close()
|
|
||||||
sendJob?.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = meta["name"].string ?: "ChannelPort[${hashCode().toString(16)}]"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send UTF-8 encoded string
|
|
||||||
*/
|
|
||||||
public suspend fun AsynchronousPort.send(string: String): Unit = send(string.encodeToByteArray())
|
|
@ -1,54 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import space.kscience.dataforge.context.*
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A DataForge plugin for managing ports
|
|
||||||
*/
|
|
||||||
public class Ports : AbstractPlugin() {
|
|
||||||
|
|
||||||
override val tag: PluginTag get() = Companion.tag
|
|
||||||
|
|
||||||
private val synchronousPortFactories by lazy {
|
|
||||||
context.gather<Factory<SynchronousPort>>(SYNCHRONOUS_PORT_TYPE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val asynchronousPortFactories by lazy {
|
|
||||||
context.gather<Factory<AsynchronousPort>>(ASYNCHRONOUS_PORT_TYPE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [AsynchronousPort] according to specification
|
|
||||||
*/
|
|
||||||
public fun buildAsynchronousPort(meta: Meta): AsynchronousPort {
|
|
||||||
val type by meta.string { error("Port type is not defined") }
|
|
||||||
val factory = asynchronousPortFactories.entries
|
|
||||||
.firstOrNull { it.key.toString() == type }?.value
|
|
||||||
?: error("Port factory for type $type not found")
|
|
||||||
return factory.build(context, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [SynchronousPort] according to specification or wrap an asynchronous implementation
|
|
||||||
*/
|
|
||||||
public fun buildSynchronousPort(meta: Meta): SynchronousPort {
|
|
||||||
val type by meta.string { error("Port type is not defined") }
|
|
||||||
val factory = synchronousPortFactories.entries
|
|
||||||
.firstOrNull { it.key.toString() == type }?.value
|
|
||||||
?: return buildAsynchronousPort(meta).asSynchronousPort()
|
|
||||||
return factory.build(context, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object : PluginFactory<Ports> {
|
|
||||||
|
|
||||||
override val tag: PluginTag = PluginTag("controls.ports", group = PluginTag.DATAFORGE_GROUP)
|
|
||||||
|
|
||||||
public const val ASYNCHRONOUS_PORT_TYPE: String = "controls.asynchronousPort"
|
|
||||||
public const val SYNCHRONOUS_PORT_TYPE: String = "controls.synchronousPort"
|
|
||||||
|
|
||||||
override fun build(context: Context, meta: Meta): Ports = Ports()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.io.Buffer
|
|
||||||
import kotlinx.io.Source
|
|
||||||
import kotlinx.io.readByteArray
|
|
||||||
import space.kscience.controls.api.LifecycleState
|
|
||||||
import space.kscience.controls.api.WithLifeCycle
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.ContextAware
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A port handler for synchronous (request-response) communication with a port.
|
|
||||||
* Only one request could be active at a time (others are suspended).
|
|
||||||
*/
|
|
||||||
public interface SynchronousPort : ContextAware, WithLifeCycle {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a single message and wait for the flow of response chunks.
|
|
||||||
* The consumer is responsible for calling a terminal operation on the flow.
|
|
||||||
*/
|
|
||||||
public suspend fun <R> respond(
|
|
||||||
request: ByteArray,
|
|
||||||
transform: suspend Flow<ByteArray>.() -> R,
|
|
||||||
): R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronously read fixed size response to a given [request]. Discard additional response bytes.
|
|
||||||
*/
|
|
||||||
public suspend fun respondFixedMessageSize(
|
|
||||||
request: ByteArray,
|
|
||||||
responseSize: Int,
|
|
||||||
): ByteArray = respond(request) {
|
|
||||||
val buffer = Buffer()
|
|
||||||
takeWhile {
|
|
||||||
buffer.size < responseSize
|
|
||||||
}.collect {
|
|
||||||
buffer.write(it)
|
|
||||||
}
|
|
||||||
buffer.readByteArray(responseSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read response to a given message using [Source] abstraction
|
|
||||||
*/
|
|
||||||
public suspend fun <R> SynchronousPort.respondAsSource(
|
|
||||||
request: ByteArray,
|
|
||||||
transform: suspend Source.() -> R,
|
|
||||||
): R = respond(request) {
|
|
||||||
//suspend until the response is fully read
|
|
||||||
coroutineScope {
|
|
||||||
val buffer = Buffer()
|
|
||||||
val collectJob = onEach { buffer.write(it) }.launchIn(this)
|
|
||||||
val res = transform(buffer)
|
|
||||||
//cancel collection when the result is achieved
|
|
||||||
collectJob.cancel()
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SynchronousOverAsynchronousPort(
|
|
||||||
val port: AsynchronousPort,
|
|
||||||
val mutex: Mutex,
|
|
||||||
) : SynchronousPort {
|
|
||||||
|
|
||||||
override val context: Context get() = port.context
|
|
||||||
|
|
||||||
override suspend fun start() {
|
|
||||||
if (port.lifecycleState == LifecycleState.STOPPED) port.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val lifecycleState: LifecycleState get() = port.lifecycleState
|
|
||||||
|
|
||||||
override suspend fun stop() {
|
|
||||||
if (port.lifecycleState == LifecycleState.STARTED) port.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <R> respond(
|
|
||||||
request: ByteArray,
|
|
||||||
transform: suspend Flow<ByteArray>.() -> R,
|
|
||||||
): R = mutex.withLock {
|
|
||||||
port.send(request)
|
|
||||||
transform(port.subscribe())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a synchronous wrapper for an asynchronous port.
|
|
||||||
* Optionally provide external [mutex] for operation synchronization.
|
|
||||||
*
|
|
||||||
* If the [AsynchronousPort] is called directly, it could violate [SynchronousPort] contract
|
|
||||||
* of only one request running simultaneously.
|
|
||||||
*/
|
|
||||||
public fun AsynchronousPort.asSynchronousPort(mutex: Mutex = Mutex()): SynchronousPort =
|
|
||||||
SynchronousOverAsynchronousPort(this, mutex)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send request and read incoming data blocks until the delimiter is encountered
|
|
||||||
*/
|
|
||||||
public suspend fun SynchronousPort.respondWithDelimiter(
|
|
||||||
data: ByteArray,
|
|
||||||
delimiter: ByteArray,
|
|
||||||
): ByteArray = respond(data) {
|
|
||||||
withDelimiter(delimiter).first()
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun SynchronousPort.respondStringWithDelimiter(
|
|
||||||
data: String,
|
|
||||||
delimiter: String,
|
|
||||||
): String = respond(data.encodeToByteArray()) {
|
|
||||||
withStringDelimiter(delimiter).first()
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.io.Buffer
|
|
||||||
import kotlinx.io.Source
|
|
||||||
import space.kscience.dataforge.io.Binary
|
|
||||||
|
|
||||||
public fun Binary.readShort(position: Int): Short = read(position) { readShort() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consume given flow of [ByteArray] as [Source]. The subscription is canceled when [scope] is closed.
|
|
||||||
*/
|
|
||||||
public fun Flow<ByteArray>.consumeAsSource(scope: CoroutineScope): Source {
|
|
||||||
val buffer = Buffer()
|
|
||||||
//subscription is canceled when the scope is canceled
|
|
||||||
onEach {
|
|
||||||
buffer.write(it)
|
|
||||||
}.launchIn(scope)
|
|
||||||
|
|
||||||
return buffer
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
|
||||||
import kotlinx.coroutines.flow.transform
|
|
||||||
import kotlinx.io.Buffer
|
|
||||||
import kotlinx.io.readByteArray
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform byte fragments into complete phrases using given delimiter. Not thread safe.
|
|
||||||
*
|
|
||||||
* TODO add type wrapper for phrases
|
|
||||||
*/
|
|
||||||
public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray> {
|
|
||||||
require(delimiter.isNotEmpty()) { "Delimiter must not be empty" }
|
|
||||||
|
|
||||||
val output = Buffer()
|
|
||||||
var matcherPosition = 0
|
|
||||||
|
|
||||||
onCompletion {
|
|
||||||
output.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform { chunk ->
|
|
||||||
chunk.forEach { byte ->
|
|
||||||
output.writeByte(byte)
|
|
||||||
//matching current symbol in delimiter
|
|
||||||
if (byte == delimiter[matcherPosition]) {
|
|
||||||
matcherPosition++
|
|
||||||
if (matcherPosition == delimiter.size) {
|
|
||||||
//full match achieved, sending result
|
|
||||||
emit(output.readByteArray())
|
|
||||||
output.clear()
|
|
||||||
matcherPosition = 0
|
|
||||||
}
|
|
||||||
} else if (matcherPosition > 0) {
|
|
||||||
//Reset matcher since full match not achieved
|
|
||||||
matcherPosition = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Flow<ByteArray>.withFixedMessageSize(messageSize: Int): Flow<ByteArray> {
|
|
||||||
require(messageSize > 0) { "Message size should be positive" }
|
|
||||||
|
|
||||||
val output = Buffer()
|
|
||||||
|
|
||||||
onCompletion {
|
|
||||||
output.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform { chunk ->
|
|
||||||
val remaining: Int = (messageSize - output.size).toInt()
|
|
||||||
if (chunk.size >= remaining) {
|
|
||||||
output.write(chunk, endIndex = remaining)
|
|
||||||
emit(output.readByteArray())
|
|
||||||
output.clear()
|
|
||||||
//write the remaining chunk fragment
|
|
||||||
if(chunk.size> remaining) {
|
|
||||||
output.write(chunk, startIndex = remaining)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.write(chunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform byte fragments into utf-8 phrases using utf-8 delimiter
|
|
||||||
*/
|
|
||||||
public fun Flow<ByteArray>.withStringDelimiter(delimiter: String): Flow<String> {
|
|
||||||
return withDelimiter(delimiter.encodeToByteArray()).map { it.decodeToString() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flow of delimited phrases
|
|
||||||
*/
|
|
||||||
public fun AsynchronousPort.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> = subscribe().withDelimiter(delimiter)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flow of delimited phrases with string content
|
|
||||||
*/
|
|
||||||
public fun AsynchronousPort.stringsDelimitedIncoming(delimiter: String): Flow<String> = subscribe().withStringDelimiter(delimiter)
|
|
@ -1,230 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
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.debug
|
|
||||||
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 kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a meta [item] to [device]
|
|
||||||
*/
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
|
||||||
private suspend fun <D : Device, T> MutableDevicePropertySpec<D, T>.writeMeta(device: D, item: Meta) {
|
|
||||||
write(device, converter.readOrNull(item) ?: error("Meta $item could not be read with $converter"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read Meta item from the [device]
|
|
||||||
*/
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
|
||||||
private suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? =
|
|
||||||
read(device)?.let(converter::convert)
|
|
||||||
|
|
||||||
|
|
||||||
private suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
|
||||||
device: D,
|
|
||||||
item: Meta,
|
|
||||||
): Meta? {
|
|
||||||
val arg: I = inputConverter.readOrNull(item) ?: error("Failed to convert $item with $inputConverter")
|
|
||||||
val res = execute(device, arg)
|
|
||||||
return res?.let { outputConverter.convert(res) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A base abstractions for [Device], introducing specifications for properties
|
|
||||||
*/
|
|
||||||
public abstract class DeviceBase<D : Device>(
|
|
||||||
final override val context: Context,
|
|
||||||
final override val meta: Meta = Meta.EMPTY,
|
|
||||||
) : CachingDevice {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection of property specifications
|
|
||||||
*/
|
|
||||||
public abstract val properties: Map<String, DevicePropertySpec<D, *>>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection of action specifications
|
|
||||||
*/
|
|
||||||
public abstract val actions: Map<String, DeviceActionSpec<D, *, *>>
|
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
|
||||||
get() = properties.values.map { it.descriptor }
|
|
||||||
|
|
||||||
override val actionDescriptors: Collection<ActionDescriptor>
|
|
||||||
get() = actions.values.map { it.descriptor }
|
|
||||||
|
|
||||||
|
|
||||||
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow(
|
|
||||||
replay = meta["message.buffer"].int ?: 1000,
|
|
||||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
|
||||||
)
|
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext = context.newCoroutineContext(
|
|
||||||
SupervisorJob(context.coroutineContext[Job]) +
|
|
||||||
CoroutineName("Device $id") +
|
|
||||||
CoroutineExceptionHandler { _, throwable ->
|
|
||||||
launch {
|
|
||||||
sharedMessageFlow.emit(
|
|
||||||
DeviceErrorMessage(
|
|
||||||
errorMessage = throwable.message,
|
|
||||||
errorType = throwable::class.simpleName,
|
|
||||||
errorStackTrace = throwable.stackTraceToString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
logger.error(throwable) { "Exception in device $id" }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logical state store
|
|
||||||
*/
|
|
||||||
private val logicalState: HashMap<String, Meta?> = HashMap()
|
|
||||||
|
|
||||||
public override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
internal val self: D
|
|
||||||
get() = this as D
|
|
||||||
|
|
||||||
private val stateLock = Mutex()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update logical property state and notify listeners
|
|
||||||
*/
|
|
||||||
protected suspend fun propertyChanged(propertyName: String, value: Meta?) {
|
|
||||||
if (value != logicalState[propertyName]) {
|
|
||||||
stateLock.withLock {
|
|
||||||
logicalState[propertyName] = value
|
|
||||||
}
|
|
||||||
if (value != null) {
|
|
||||||
sharedMessageFlow.emit(PropertyChangedMessage(propertyName, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the device that a property with [spec] value is changed
|
|
||||||
*/
|
|
||||||
protected suspend fun <T> propertyChanged(spec: DevicePropertySpec<D, T>, value: T) {
|
|
||||||
propertyChanged(spec.name, spec.converter.convert(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
|
|
||||||
* The logical state is updated after read
|
|
||||||
*/
|
|
||||||
override suspend fun readProperty(propertyName: String): Meta {
|
|
||||||
val spec = properties[propertyName] ?: error("Property with name $propertyName not found")
|
|
||||||
val meta = spec.readMeta(self) ?: error("Failed to read property $propertyName")
|
|
||||||
propertyChanged(propertyName, meta)
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read property if it exists and read correctly. Return null otherwise.
|
|
||||||
*/
|
|
||||||
public suspend fun readPropertyOrNull(propertyName: String): Meta? {
|
|
||||||
val spec = properties[propertyName] ?: return null
|
|
||||||
val meta = spec.readMeta(self) ?: return null
|
|
||||||
propertyChanged(propertyName, meta)
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperty(propertyName: String): Meta? = logicalState[propertyName]
|
|
||||||
|
|
||||||
override suspend fun invalidate(propertyName: String) {
|
|
||||||
stateLock.withLock {
|
|
||||||
logicalState.remove(propertyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeProperty(propertyName: String, value: Meta): Unit {
|
|
||||||
//bypass property setting if it already has that value
|
|
||||||
if (logicalState[propertyName] == value) {
|
|
||||||
logger.debug { "Skipping setting $propertyName to $value because value is already set" }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (val property = properties[propertyName]) {
|
|
||||||
null -> {
|
|
||||||
//If there are no registered physical properties with given name, write a logical one.
|
|
||||||
propertyChanged(propertyName, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
is MutableDevicePropertySpec -> {
|
|
||||||
//if there is a writeable property with a given name, invalidate logical and write physical
|
|
||||||
invalidate(propertyName)
|
|
||||||
property.writeMeta(self, value)
|
|
||||||
// perform read after writing if the writer did not set the value and the value is still in invalid state
|
|
||||||
if (logicalState[propertyName] == null) {
|
|
||||||
val meta = property.readMeta(self)
|
|
||||||
propertyChanged(propertyName, meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
error("Property $property is not writeable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ?: Meta.EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
final override var lifecycleState: LifecycleState = LifecycleState.STOPPED
|
|
||||||
private set
|
|
||||||
|
|
||||||
|
|
||||||
private suspend fun setLifecycleState(lifecycleState: LifecycleState) {
|
|
||||||
this.lifecycleState = lifecycleState
|
|
||||||
sharedMessageFlow.emit(
|
|
||||||
DeviceLifeCycleMessage(lifecycleState)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open suspend fun onStart() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
final override suspend fun start() {
|
|
||||||
if (lifecycleState == LifecycleState.STOPPED) {
|
|
||||||
super.start()
|
|
||||||
setLifecycleState(LifecycleState.STARTING)
|
|
||||||
onStart()
|
|
||||||
setLifecycleState(LifecycleState.STARTED)
|
|
||||||
} else {
|
|
||||||
logger.debug { "Device $this is already started" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open suspend fun onStop() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
final override suspend fun stop() {
|
|
||||||
onStop()
|
|
||||||
setLifecycleState(LifecycleState.STOPPED)
|
|
||||||
super.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
abstract override fun toString(): String
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A device generated from specification
|
|
||||||
* @param D recursive self-type for properties and actions
|
|
||||||
*/
|
|
||||||
public open class DeviceBySpec<D : Device>(
|
|
||||||
public val spec: DeviceSpec<in D>,
|
|
||||||
context: Context,
|
|
||||||
meta: Meta = Meta.EMPTY,
|
|
||||||
) : DeviceBase<D>(context, meta) {
|
|
||||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
|
||||||
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
|
||||||
|
|
||||||
override suspend fun onStart(): Unit = with(spec) {
|
|
||||||
self.onOpen()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onStop(): Unit = with(spec){
|
|
||||||
self.onClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String = "Device(spec=$spec)"
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
|
|
||||||
internal object DeviceMetaPropertySpec : DevicePropertySpec<Device, Meta> {
|
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor("@meta")
|
|
||||||
|
|
||||||
override val converter: MetaConverter<Meta> = MetaConverter.meta
|
|
||||||
|
|
||||||
@InternalDeviceAPI
|
|
||||||
override suspend fun read(device: Device): Meta = device.meta
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import space.kscience.controls.api.*
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This API is internal and should not be used in user code
|
|
||||||
*/
|
|
||||||
@RequiresOptIn("This API should not be called outside of Device internals")
|
|
||||||
public annotation class InternalDeviceAPI
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specification for a device read-only property
|
|
||||||
*/
|
|
||||||
public interface DevicePropertySpec<in D, T> {
|
|
||||||
/**
|
|
||||||
* Property descriptor
|
|
||||||
*/
|
|
||||||
public val descriptor: PropertyDescriptor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Meta item converter for the resulting type
|
|
||||||
*/
|
|
||||||
public val converter: MetaConverter<T>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read physical value from the given [device]
|
|
||||||
*/
|
|
||||||
@InternalDeviceAPI
|
|
||||||
public suspend fun read(device: D): T?
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property name should be unique in a device
|
|
||||||
*/
|
|
||||||
public val DevicePropertySpec<*, *>.name: String get() = descriptor.name
|
|
||||||
|
|
||||||
|
|
||||||
public interface MutableDevicePropertySpec<in D : Device, T> : DevicePropertySpec<D, T> {
|
|
||||||
/**
|
|
||||||
* Write physical value to a device
|
|
||||||
*/
|
|
||||||
@InternalDeviceAPI
|
|
||||||
public suspend fun write(device: D, value: T)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DeviceActionSpec<in D, I, O> {
|
|
||||||
/**
|
|
||||||
* Action descriptor
|
|
||||||
*/
|
|
||||||
public val descriptor: ActionDescriptor
|
|
||||||
|
|
||||||
public val inputConverter: MetaConverter<I>
|
|
||||||
|
|
||||||
public val outputConverter: MetaConverter<O>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute action on a device
|
|
||||||
*/
|
|
||||||
public suspend fun execute(device: D, input: I): O
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name. Should be unique in the device
|
|
||||||
*/
|
|
||||||
public val DeviceActionSpec<*, *, *>.name: String get() = descriptor.name
|
|
||||||
|
|
||||||
public suspend fun <T, D : Device> D.read(propertySpec: DevicePropertySpec<D, T>): T =
|
|
||||||
propertySpec.converter.readOrNull(readProperty(propertySpec.name)) ?: error("Property read result is not valid")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read typed value and update/push event if needed.
|
|
||||||
* Return null if property read is not successful or property is undefined.
|
|
||||||
*/
|
|
||||||
public suspend fun <T, D : DeviceBase<D>> D.readOrNull(propertySpec: DevicePropertySpec<D, T>): T? =
|
|
||||||
readPropertyOrNull(propertySpec.name)?.let(propertySpec.converter::readOrNull)
|
|
||||||
|
|
||||||
public suspend fun <T, D : Device> D.getOrRead(propertySpec: DevicePropertySpec<D, T>): T =
|
|
||||||
propertySpec.converter.read(getOrReadProperty(propertySpec.name))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write typed property state and invalidate logical state
|
|
||||||
*/
|
|
||||||
public suspend fun <T, D : Device> D.write(propertySpec: MutableDevicePropertySpec<D, T>, value: T) {
|
|
||||||
writeProperty(propertySpec.name, propertySpec.converter.convert(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fire and forget variant of property writing. Actual write is performed asynchronously on a [Device] scope
|
|
||||||
*/
|
|
||||||
public fun <T, D : Device> D.writeAsync(propertySpec: MutableDevicePropertySpec<D, T>, value: T): Job = launch {
|
|
||||||
write(propertySpec, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type safe flow of property changes for given property
|
|
||||||
*/
|
|
||||||
public fun <D : Device, T> D.propertyFlow(spec: DevicePropertySpec<D, T>): Flow<T> = messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == spec.name }
|
|
||||||
.mapNotNull { spec.converter.read(it.value) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type safe property change listener. Uses the device [CoroutineScope].
|
|
||||||
*/
|
|
||||||
public fun <D : Device, T> D.onPropertyChange(
|
|
||||||
spec: DevicePropertySpec<D, T>,
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
callback: suspend PropertyChangedMessage.(T) -> Unit,
|
|
||||||
): Job = messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == spec.name }
|
|
||||||
.onEach { change ->
|
|
||||||
val newValue = spec.converter.read(change.value)
|
|
||||||
if (newValue != null) {
|
|
||||||
change.callback(newValue)
|
|
||||||
}
|
|
||||||
}.launchIn(scope)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call [callback] on initial property value and each value change
|
|
||||||
*/
|
|
||||||
public fun <D : Device, T> D.useProperty(
|
|
||||||
spec: DevicePropertySpec<D, T>,
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
callback: suspend (T) -> Unit,
|
|
||||||
): Job = scope.launch {
|
|
||||||
callback(read(spec))
|
|
||||||
messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == spec.name }
|
|
||||||
.collect { change ->
|
|
||||||
val newValue = spec.converter.readOrNull(change.value)
|
|
||||||
if (newValue != null) {
|
|
||||||
callback(newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the logical state of a property
|
|
||||||
*/
|
|
||||||
public suspend fun <D : CachingDevice> D.invalidate(propertySpec: DevicePropertySpec<D, *>) {
|
|
||||||
invalidate(propertySpec.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the action with name according to [actionSpec]
|
|
||||||
*/
|
|
||||||
public suspend fun <I, O, D : Device> D.execute(actionSpec: DeviceActionSpec<D, I, O>, input: I): O =
|
|
||||||
actionSpec.execute(this, input)
|
|
||||||
|
|
||||||
public suspend fun <O, D : Device> D.execute(actionSpec: DeviceActionSpec<D, Unit, O>): O =
|
|
||||||
actionSpec.execute(this, Unit)
|
|
@ -1,207 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import space.kscience.controls.api.*
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
public object UnitMetaConverter : MetaConverter<Unit> {
|
|
||||||
|
|
||||||
override fun readOrNull(source: Meta): Unit = Unit
|
|
||||||
|
|
||||||
override fun convert(obj: Unit): Meta = Meta.EMPTY
|
|
||||||
}
|
|
||||||
|
|
||||||
public val MetaConverter.Companion.unit: MetaConverter<Unit> get() = UnitMetaConverter
|
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
|
||||||
public abstract class DeviceSpec<D : Device> {
|
|
||||||
//initializing the metadata property for everyone
|
|
||||||
private val _properties = hashMapOf<String, DevicePropertySpec<D, *>>(
|
|
||||||
DeviceMetaPropertySpec.name to DeviceMetaPropertySpec
|
|
||||||
)
|
|
||||||
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
|
||||||
|
|
||||||
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
|
||||||
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
|
|
||||||
|
|
||||||
|
|
||||||
public open suspend fun D.onOpen() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public open suspend fun D.onClose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
|
|
||||||
_properties[deviceProperty.name] = deviceProperty
|
|
||||||
return deviceProperty
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> property(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> T?,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
|
|
||||||
val propertyName = name ?: property.name
|
|
||||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
|
||||||
|
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
|
|
||||||
converter.descriptor?.let { converterDescriptor ->
|
|
||||||
metaDescriptor {
|
|
||||||
from(converterDescriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fromSpec(property)
|
|
||||||
descriptorBuilder()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val converter: MetaConverter<T> = converter
|
|
||||||
|
|
||||||
override suspend fun read(device: D): T? =
|
|
||||||
withContext(device.coroutineContext) { device.read(propertyName) }
|
|
||||||
}
|
|
||||||
registerProperty(deviceProperty)
|
|
||||||
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
|
|
||||||
deviceProperty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> mutableProperty(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> T?,
|
|
||||||
write: suspend D.(propertyName: String, value: T) -> Unit,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
|
||||||
val propertyName = name ?: property.name
|
|
||||||
val deviceProperty = object : MutableDevicePropertySpec<D, T> {
|
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(
|
|
||||||
propertyName,
|
|
||||||
mutable = true
|
|
||||||
).apply {
|
|
||||||
converter.descriptor?.let { converterDescriptor ->
|
|
||||||
metaDescriptor {
|
|
||||||
from(converterDescriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fromSpec(property)
|
|
||||||
descriptorBuilder()
|
|
||||||
}
|
|
||||||
override val converter: MetaConverter<T> = converter
|
|
||||||
|
|
||||||
override suspend fun read(device: D): T? =
|
|
||||||
withContext(device.coroutineContext) { device.read(propertyName) }
|
|
||||||
|
|
||||||
override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) {
|
|
||||||
device.write(propertyName, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registerProperty(deviceProperty)
|
|
||||||
ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>> { _, _ ->
|
|
||||||
deviceProperty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
|
||||||
_actions[deviceAction.name] = deviceAction
|
|
||||||
return deviceAction
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <I, O> action(
|
|
||||||
inputConverter: MetaConverter<I>,
|
|
||||||
outputConverter: MetaConverter<O>,
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
execute: suspend D.(I) -> O,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
|
|
||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
|
||||||
val actionName = name ?: property.name
|
|
||||||
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
|
||||||
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply {
|
|
||||||
inputConverter.descriptor?.let { converterDescriptor ->
|
|
||||||
inputMetaDescriptor = MetaDescriptor {
|
|
||||||
from(converterDescriptor)
|
|
||||||
from(inputMetaDescriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outputConverter.descriptor?.let { converterDescriptor ->
|
|
||||||
outputMetaDescriptor = MetaDescriptor {
|
|
||||||
from(converterDescriptor)
|
|
||||||
from(outputMetaDescriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fromSpec(property)
|
|
||||||
descriptorBuilder()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val inputConverter: MetaConverter<I> = inputConverter
|
|
||||||
override val outputConverter: MetaConverter<O> = outputConverter
|
|
||||||
|
|
||||||
override suspend fun execute(device: D, input: I): O = withContext(device.coroutineContext) {
|
|
||||||
device.execute(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_actions[actionName] = deviceAction
|
|
||||||
ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>> { _, _ ->
|
|
||||||
deviceAction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An action that takes no parameters and returns no values
|
|
||||||
*/
|
|
||||||
public fun <D : Device> DeviceSpec<D>.unitAction(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
execute: suspend D.() -> Unit,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Unit, Unit>>> =
|
|
||||||
action(
|
|
||||||
MetaConverter.Companion.unit,
|
|
||||||
MetaConverter.Companion.unit,
|
|
||||||
descriptorBuilder,
|
|
||||||
name
|
|
||||||
) {
|
|
||||||
execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An action that takes [Meta] and returns [Meta]. No conversions are done
|
|
||||||
*/
|
|
||||||
public fun <D : Device> DeviceSpec<D>.metaAction(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
execute: suspend D.(Meta) -> Meta,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
|
|
||||||
action(
|
|
||||||
MetaConverter.Companion.meta,
|
|
||||||
MetaConverter.Companion.meta,
|
|
||||||
descriptorBuilder,
|
|
||||||
name
|
|
||||||
) {
|
|
||||||
execute(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throw an exception if device does not have all properties and actions defined by this specification
|
|
||||||
*/
|
|
||||||
public fun DeviceSpec<*>.validate(device: Device) {
|
|
||||||
properties.map { it.value.descriptor }.forEach { specProperty ->
|
|
||||||
check(specProperty in device.propertyDescriptors) { "Property ${specProperty.name} not registered in ${device.id}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.map { it.value.descriptor }.forEach { specAction ->
|
|
||||||
check(specAction in device.actionDescriptors) { "Action ${specAction.name} not registered in ${device.id}" }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.manager.getCoroutineDispatcher
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do a recurring (with a fixed delay) task on a device.
|
|
||||||
*/
|
|
||||||
public fun <D : Device> D.doRecurring(
|
|
||||||
interval: Duration,
|
|
||||||
debugTaskName: String? = null,
|
|
||||||
task: suspend D.() -> Unit,
|
|
||||||
): Job {
|
|
||||||
val taskName = debugTaskName ?: "task[${task.hashCode().toString(16)}]"
|
|
||||||
val dispatcher = getCoroutineDispatcher()
|
|
||||||
return launch(CoroutineName(taskName) + dispatcher) {
|
|
||||||
while (isActive) {
|
|
||||||
delay(interval)
|
|
||||||
//launch in parent scope to properly evaluate exceptions
|
|
||||||
this@doRecurring.launch(CoroutineName("$taskName-recurring") + dispatcher) {
|
|
||||||
task()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform a recurring asynchronous read action and return a flow of results.
|
|
||||||
* The flow is lazy, so action is not performed unless flow is consumed.
|
|
||||||
* The flow uses caller context. To call it on device context, use `flowOn(coroutineContext)`.
|
|
||||||
*
|
|
||||||
* The flow is canceled when the device scope is canceled
|
|
||||||
*/
|
|
||||||
public fun <D : Device, R> D.readRecurring(
|
|
||||||
interval: Duration,
|
|
||||||
debugTaskName: String? = null,
|
|
||||||
reader: suspend D.() -> R,
|
|
||||||
): Flow<R> = flow {
|
|
||||||
doRecurring(interval, debugTaskName) {
|
|
||||||
emit(reader())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
internal expect fun PropertyDescriptor.fromSpec(property: KProperty<*>)
|
|
||||||
|
|
||||||
internal expect fun ActionDescriptor.fromSpec(property: KProperty<*>)
|
|
@ -1,211 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.controls.api.metaDescriptor
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaConverter
|
|
||||||
import space.kscience.dataforge.meta.ValueType
|
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KMutableProperty1
|
|
||||||
import kotlin.reflect.KProperty1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A read-only device property that delegates reading to a device [KProperty1]
|
|
||||||
*/
|
|
||||||
public fun <T, D : Device> DeviceSpec<D>.property(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
readOnlyProperty: KProperty1<D, T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> = property(
|
|
||||||
converter,
|
|
||||||
descriptorBuilder,
|
|
||||||
name = readOnlyProperty.name,
|
|
||||||
read = { readOnlyProperty.get(this) }
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutable property that delegates reading and writing to a device [KMutableProperty1]
|
|
||||||
*/
|
|
||||||
public fun <T, D : Device> DeviceSpec<D>.mutableProperty(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
readWriteProperty: KMutableProperty1<D, T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
|
||||||
mutableProperty(
|
|
||||||
converter,
|
|
||||||
descriptorBuilder,
|
|
||||||
readWriteProperty.name,
|
|
||||||
read = { _ -> readWriteProperty.get(this) },
|
|
||||||
write = { _, value: T -> readWriteProperty.set(this, value) }
|
|
||||||
)
|
|
||||||
|
|
||||||
//read only delegates
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a read-only logical property
|
|
||||||
* (without a corresponding physical state or with a state that is updated asynchronously) for a device
|
|
||||||
*/
|
|
||||||
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.property(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
|
|
||||||
property(
|
|
||||||
converter,
|
|
||||||
descriptorBuilder,
|
|
||||||
name,
|
|
||||||
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Boolean?
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
|
||||||
MetaConverter.boolean,
|
|
||||||
{
|
|
||||||
metaDescriptor {
|
|
||||||
valueType(ValueType.BOOLEAN)
|
|
||||||
}
|
|
||||||
descriptorBuilder()
|
|
||||||
},
|
|
||||||
name,
|
|
||||||
read
|
|
||||||
)
|
|
||||||
|
|
||||||
private inline fun numberDescriptor(
|
|
||||||
crossinline descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
|
||||||
): PropertyDescriptor.() -> Unit = {
|
|
||||||
metaDescriptor {
|
|
||||||
valueType(ValueType.NUMBER)
|
|
||||||
}
|
|
||||||
descriptorBuilder()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.numberProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Number?
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
|
|
||||||
MetaConverter.number,
|
|
||||||
numberDescriptor(descriptorBuilder),
|
|
||||||
name,
|
|
||||||
read
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Double?
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
|
|
||||||
MetaConverter.double,
|
|
||||||
numberDescriptor(descriptorBuilder),
|
|
||||||
name,
|
|
||||||
read
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.stringProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> String?
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
|
||||||
MetaConverter.string,
|
|
||||||
{
|
|
||||||
metaDescriptor {
|
|
||||||
valueType(ValueType.STRING)
|
|
||||||
}
|
|
||||||
descriptorBuilder()
|
|
||||||
},
|
|
||||||
name,
|
|
||||||
read
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.metaProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Meta?
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
|
||||||
MetaConverter.meta,
|
|
||||||
{
|
|
||||||
metaDescriptor {
|
|
||||||
valueType(ValueType.STRING)
|
|
||||||
}
|
|
||||||
descriptorBuilder()
|
|
||||||
},
|
|
||||||
name,
|
|
||||||
read
|
|
||||||
)
|
|
||||||
|
|
||||||
//read-write delegates
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a mutable logical property
|
|
||||||
* (without a corresponding physical state or with a state that is updated asynchronously) for a device
|
|
||||||
*/
|
|
||||||
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.mutableProperty(
|
|
||||||
converter: MetaConverter<T>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
|
|
||||||
mutableProperty(
|
|
||||||
converter,
|
|
||||||
descriptorBuilder,
|
|
||||||
name,
|
|
||||||
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
|
|
||||||
write = { propertyName, value -> writeProperty(propertyName, converter.convert(value)) }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.mutableBooleanProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Boolean?,
|
|
||||||
write: suspend D.(propertyName: String, value: Boolean) -> Unit
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Boolean>>> =
|
|
||||||
mutableProperty(
|
|
||||||
MetaConverter.boolean,
|
|
||||||
{
|
|
||||||
metaDescriptor {
|
|
||||||
valueType(ValueType.BOOLEAN)
|
|
||||||
}
|
|
||||||
descriptorBuilder()
|
|
||||||
},
|
|
||||||
name,
|
|
||||||
read,
|
|
||||||
write
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.mutableNumberProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Number,
|
|
||||||
write: suspend D.(propertyName: String, value: Number) -> Unit
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Number>>> =
|
|
||||||
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.mutableDoubleProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Double,
|
|
||||||
write: suspend D.(propertyName: String, value: Double) -> Unit
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Double>>> =
|
|
||||||
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.mutableStringProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> String,
|
|
||||||
write: suspend D.(propertyName: String, value: String) -> Unit
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, String>>> =
|
|
||||||
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
|
||||||
|
|
||||||
public fun <D : Device> DeviceSpec<D>.mutableMetaProperty(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
name: String? = null,
|
|
||||||
read: suspend D.(propertyName: String) -> Meta,
|
|
||||||
write: suspend D.(propertyName: String, value: Meta) -> Unit
|
|
||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
|
|
||||||
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
|
|
@ -1,17 +0,0 @@
|
|||||||
package space.kscience.controls.api
|
|
||||||
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import space.kscience.controls.misc.asMeta
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class MessageTest {
|
|
||||||
@Test
|
|
||||||
fun messageSerialization() {
|
|
||||||
val changedMessage = PropertyChangedMessage("test", 22.0.asMeta())
|
|
||||||
val json = Json.encodeToString(changedMessage)
|
|
||||||
val reconstructed: PropertyChangedMessage = Json.decodeFromString(json)
|
|
||||||
assertEquals(changedMessage.time, reconstructed.time)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>){}
|
|
||||||
|
|
||||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}
|
|
@ -1,177 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import space.kscience.controls.api.LifecycleState
|
|
||||||
import space.kscience.dataforge.context.*
|
|
||||||
import space.kscience.dataforge.meta.*
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.AsynchronousCloseException
|
|
||||||
import java.nio.channels.ByteChannel
|
|
||||||
import java.nio.channels.DatagramChannel
|
|
||||||
import java.nio.channels.SocketChannel
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the contents of this buffer to an array
|
|
||||||
*/
|
|
||||||
public fun ByteBuffer.copyToArray(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,
|
|
||||||
meta: Meta,
|
|
||||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
|
||||||
channelBuilder: suspend () -> ByteChannel,
|
|
||||||
) : AbstractAsynchronousPort(context, meta, coroutineContext) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A handler to await port connection
|
|
||||||
*/
|
|
||||||
private val futureChannel: Deferred<ByteChannel> = scope.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
|
|
||||||
channelBuilder()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var listenerJob: Job? = null
|
|
||||||
|
|
||||||
override val lifecycleState: LifecycleState
|
|
||||||
get() = if(listenerJob?.isActive == true) LifecycleState.STARTED else LifecycleState.STOPPED
|
|
||||||
|
|
||||||
override fun onOpen() {
|
|
||||||
listenerJob = scope.launch(Dispatchers.IO) {
|
|
||||||
val channel = futureChannel.await()
|
|
||||||
val buffer = ByteBuffer.allocate(1024)
|
|
||||||
while (isActive && channel.isOpen) {
|
|
||||||
try {
|
|
||||||
val num = channel.read(buffer)
|
|
||||||
if (num > 0) {
|
|
||||||
receive(buffer.copyToArray(num))
|
|
||||||
}
|
|
||||||
if (num < 0) cancel("The input channel is exhausted")
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
if (ex is AsynchronousCloseException) {
|
|
||||||
logger.info { "Channel $channel closed" }
|
|
||||||
} else {
|
|
||||||
logger.error(ex) { "Channel read error, retrying in 1 second" }
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
|
|
||||||
futureChannel.await().write(ByteBuffer.wrap(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
override suspend fun stop() {
|
|
||||||
listenerJob?.cancel()
|
|
||||||
if (futureChannel.isCompleted) {
|
|
||||||
futureChannel.getCompleted().close()
|
|
||||||
}
|
|
||||||
super.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Factory] for TCP connections
|
|
||||||
*/
|
|
||||||
public object TcpPort : Factory<AsynchronousPort> {
|
|
||||||
|
|
||||||
public fun build(
|
|
||||||
context: Context,
|
|
||||||
host: String,
|
|
||||||
port: Int,
|
|
||||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
|
||||||
): ChannelPort {
|
|
||||||
val meta = Meta {
|
|
||||||
"name" put "tcp://$host:$port"
|
|
||||||
"type" put "tcp"
|
|
||||||
"host" put host
|
|
||||||
"port" put port
|
|
||||||
}
|
|
||||||
return ChannelPort(context, meta, coroutineContext) {
|
|
||||||
SocketChannel.open(InetSocketAddress(host, port))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and open TCP port
|
|
||||||
*/
|
|
||||||
public suspend fun start(
|
|
||||||
context: Context,
|
|
||||||
host: String,
|
|
||||||
port: Int,
|
|
||||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
|
||||||
): ChannelPort = build(context, host, port, coroutineContext).apply { start() }
|
|
||||||
|
|
||||||
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 build(context, host, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Factory] for UDP connections
|
|
||||||
*/
|
|
||||||
public object UdpPort : Factory<AsynchronousPort> {
|
|
||||||
|
|
||||||
public fun build(
|
|
||||||
context: Context,
|
|
||||||
remoteHost: String,
|
|
||||||
remotePort: Int,
|
|
||||||
localPort: Int? = null,
|
|
||||||
localHost: String? = null,
|
|
||||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
|
||||||
): ChannelPort {
|
|
||||||
val meta = Meta {
|
|
||||||
"name" put "udp://$remoteHost:$remotePort"
|
|
||||||
"type" put "udp"
|
|
||||||
"remoteHost" put remoteHost
|
|
||||||
"remotePort" put remotePort
|
|
||||||
localHost?.let { "localHost" put it }
|
|
||||||
localPort?.let { "localPort" put it }
|
|
||||||
}
|
|
||||||
return ChannelPort(context, meta, coroutineContext) {
|
|
||||||
DatagramChannel.open().apply {
|
|
||||||
//bind the channel to a local port to receive messages
|
|
||||||
localPort?.let { bind(InetSocketAddress(localHost ?: "localhost", it)) }
|
|
||||||
//connect to remote port to send messages
|
|
||||||
connect(InetSocketAddress(remoteHost, remotePort.toInt()))
|
|
||||||
context.logger.info { "Connected to UDP $remotePort on $remoteHost" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect a datagram channel to a remote host/port. If [localPort] is provided, it is used to bind local port for receiving messages.
|
|
||||||
*/
|
|
||||||
public suspend fun start(
|
|
||||||
context: Context,
|
|
||||||
remoteHost: String,
|
|
||||||
remotePort: Int,
|
|
||||||
localPort: Int? = null,
|
|
||||||
localHost: String = "localhost",
|
|
||||||
): ChannelPort = build(context, remoteHost, remotePort, localPort, localHost).apply { start() }
|
|
||||||
|
|
||||||
|
|
||||||
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 build(context, remoteHost, remotePort.toInt(), localPort, localHost)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package space.kscience.controls.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
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<Name, Any> = when(target){
|
|
||||||
Ports.ASYNCHRONOUS_PORT_TYPE -> mapOf(
|
|
||||||
"tcp".asName() to TcpPort,
|
|
||||||
"udp".asName() to UdpPort
|
|
||||||
)
|
|
||||||
else -> emptyMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object : PluginFactory<JvmPortsPlugin> {
|
|
||||||
|
|
||||||
override val tag: PluginTag = PluginTag("controls.ports.jvm", group = PluginTag.DATAFORGE_GROUP)
|
|
||||||
|
|
||||||
override fun build(context: Context, meta: Meta): JvmPortsPlugin = JvmPortsPlugin()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import space.kscience.controls.api.LifecycleState
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import java.net.DatagramPacket
|
|
||||||
import java.net.DatagramSocket
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A port based on [DatagramSocket] for cases, where [ChannelPort] does not work for some reason
|
|
||||||
*/
|
|
||||||
public class UdpSocketPort(
|
|
||||||
override val context: Context,
|
|
||||||
meta: Meta,
|
|
||||||
private val socket: DatagramSocket,
|
|
||||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
|
||||||
) : AbstractAsynchronousPort(context, meta, coroutineContext) {
|
|
||||||
|
|
||||||
private var listenerJob: Job? = null
|
|
||||||
|
|
||||||
override fun onOpen() {
|
|
||||||
listenerJob = context.launch(Dispatchers.IO) {
|
|
||||||
while (isActive) {
|
|
||||||
val buf = ByteArray(socket.receiveBufferSize)
|
|
||||||
|
|
||||||
val packet = DatagramPacket(
|
|
||||||
buf,
|
|
||||||
buf.size,
|
|
||||||
)
|
|
||||||
socket.receive(packet)
|
|
||||||
|
|
||||||
val bytes = packet.data.copyOfRange(
|
|
||||||
packet.offset,
|
|
||||||
packet.offset + packet.length
|
|
||||||
)
|
|
||||||
receive(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun stop() {
|
|
||||||
listenerJob?.cancel()
|
|
||||||
super.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val lifecycleState: LifecycleState
|
|
||||||
get() = if(listenerJob?.isActive == true) LifecycleState.STARTED else LifecycleState.STOPPED
|
|
||||||
|
|
||||||
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
|
|
||||||
val packet = DatagramPacket(
|
|
||||||
data,
|
|
||||||
data.size,
|
|
||||||
socket.remoteSocketAddress
|
|
||||||
)
|
|
||||||
socket.send(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import space.kscience.dataforge.descriptors.Description
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
|
||||||
|
|
||||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>) {
|
|
||||||
property.findAnnotation<Description>()?.let {
|
|
||||||
description = it.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){
|
|
||||||
property.findAnnotation<Description>()?.let {
|
|
||||||
description = it.value
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.take
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import space.kscience.dataforge.context.Global
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
|
|
||||||
internal class AsynchronousPortIOTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDelimiteredByteArrayFlow() {
|
|
||||||
val flow = flowOf("bb?b", "ddd?", ":defgb?:ddf", "34fb?:--").map { it.encodeToByteArray() }
|
|
||||||
val chunked = flow.withDelimiter("?:".encodeToByteArray())
|
|
||||||
runBlocking {
|
|
||||||
val result = chunked.toList()
|
|
||||||
assertEquals(3, result.size)
|
|
||||||
assertEquals("bb?bddd?:", result[0].decodeToString())
|
|
||||||
assertEquals("defgb?:", result[1].decodeToString())
|
|
||||||
assertEquals("ddf34fb?:", result[2].decodeToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testUdpCommunication() = runTest {
|
|
||||||
val receiver = UdpPort.start(Global, "localhost", 8811, localPort = 8812)
|
|
||||||
val sender = UdpPort.start(Global, "localhost", 8812, localPort = 8811)
|
|
||||||
|
|
||||||
delay(30)
|
|
||||||
repeat(10) {
|
|
||||||
sender.send("Line number $it\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
val res = receiver
|
|
||||||
.subscribe()
|
|
||||||
.withStringDelimiter("\n")
|
|
||||||
.take(10)
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
assertEquals("Line number 3", res[3].trim())
|
|
||||||
receiver.stop()
|
|
||||||
sender.stop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>) {}
|
|
||||||
|
|
||||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}
|
|
@ -1,9 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.ActionDescriptor
|
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>){}
|
|
||||||
|
|
||||||
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}
|
|
@ -1,21 +0,0 @@
|
|||||||
# Module controls-jupyter
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
## Artifact:
|
|
||||||
|
|
||||||
The Maven coordinates of this project are `space.kscience:controls-jupyter:0.4.0-dev-7`.
|
|
||||||
|
|
||||||
**Gradle Kotlin DSL:**
|
|
||||||
```kotlin
|
|
||||||
repositories {
|
|
||||||
maven("https://repo.kotlin.link")
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("space.kscience:controls-jupyter:0.4.0-dev-7")
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,8 +0,0 @@
|
|||||||
public final class space/kscience/controls/jupyter/ControlsJupyter : space/kscience/visionforge/jupyter/VisionForgeIntegration {
|
|
||||||
public static final field Companion Lspace/kscience/controls/jupyter/ControlsJupyter$Companion;
|
|
||||||
public fun <init> ()V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class space/kscience/controls/jupyter/ControlsJupyter$Companion {
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("space.kscience.gradle.mpp")
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
kscience {
|
|
||||||
fullStack("js/controls-jupyter.js")
|
|
||||||
useKtor()
|
|
||||||
useContextReceivers()
|
|
||||||
jupyterLibrary("space.kscience.controls.jupyter.ControlsJupyter")
|
|
||||||
dependencies {
|
|
||||||
implementation(projects.controlsVision)
|
|
||||||
implementation(libs.visionforge.jupiter)
|
|
||||||
}
|
|
||||||
jvmMain {
|
|
||||||
implementation(spclibs.logback.classic)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
import space.kscience.visionforge.html.runVisionClient
|
|
||||||
import space.kscience.visionforge.jupyter.VFNotebookClient
|
|
||||||
import space.kscience.visionforge.markup.MarkupPlugin
|
|
||||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
|
||||||
|
|
||||||
public fun main(): Unit = runVisionClient {
|
|
||||||
// plugin(DeviceManager)
|
|
||||||
// plugin(ClockManager)
|
|
||||||
plugin(PlotlyPlugin)
|
|
||||||
plugin(MarkupPlugin)
|
|
||||||
// plugin(TableVisionJsPlugin)
|
|
||||||
plugin(VFNotebookClient)
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
|||||||
package space.kscience.controls.jupyter
|
|
||||||
|
|
||||||
import org.jetbrains.kotlinx.jupyter.api.declare
|
|
||||||
import org.jetbrains.kotlinx.jupyter.api.libraries.resources
|
|
||||||
import space.kscience.controls.manager.ClockManager
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.plotly.Plot
|
|
||||||
import space.kscience.visionforge.jupyter.VisionForge
|
|
||||||
import space.kscience.visionforge.jupyter.VisionForgeIntegration
|
|
||||||
import space.kscience.visionforge.markup.MarkupPlugin
|
|
||||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
|
||||||
import space.kscience.visionforge.plotly.asVision
|
|
||||||
import space.kscience.visionforge.visionManager
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
|
||||||
public class ControlsJupyter : VisionForgeIntegration(CONTEXT.visionManager) {
|
|
||||||
|
|
||||||
override fun Builder.afterLoaded(vf: VisionForge) {
|
|
||||||
|
|
||||||
resources {
|
|
||||||
js("controls-jupyter") {
|
|
||||||
classPath("js/controls-jupyter.js")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded {
|
|
||||||
declare("context" to CONTEXT)
|
|
||||||
}
|
|
||||||
|
|
||||||
import(
|
|
||||||
"kotlin.time.*",
|
|
||||||
"kotlin.time.Duration.Companion.milliseconds",
|
|
||||||
"kotlin.time.Duration.Companion.seconds",
|
|
||||||
// "space.kscience.tables.*",
|
|
||||||
"space.kscience.dataforge.meta.*",
|
|
||||||
"space.kscience.dataforge.context.*",
|
|
||||||
"space.kscience.plotly.*",
|
|
||||||
"space.kscience.plotly.models.*",
|
|
||||||
"space.kscience.visionforge.plotly.*",
|
|
||||||
"space.kscience.controls.manager.*",
|
|
||||||
"space.kscience.controls.constructor.*",
|
|
||||||
"space.kscience.controls.vision.*",
|
|
||||||
"space.kscience.controls.spec.*"
|
|
||||||
)
|
|
||||||
|
|
||||||
// render<Table<*>> { table ->
|
|
||||||
// vf.produceHtml {
|
|
||||||
// vision { table.toVision() }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
render<Plot> { plot ->
|
|
||||||
vf.produceHtml {
|
|
||||||
vision { plot.asVision() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
private val CONTEXT: Context = Context("controls-jupyter") {
|
|
||||||
plugin(DeviceManager)
|
|
||||||
plugin(ClockManager)
|
|
||||||
plugin(PlotlyPlugin)
|
|
||||||
// plugin(TableVisionPlugin)
|
|
||||||
plugin(MarkupPlugin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
# 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.4.0-dev-7`.
|
|
||||||
|
|
||||||
**Gradle Kotlin DSL:**
|
|
||||||
```kotlin
|
|
||||||
repositories {
|
|
||||||
maven("https://repo.kotlin.link")
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("space.kscience:controls-magix:0.4.0-dev-7")
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,199 +0,0 @@
|
|||||||
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 <init> (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 <init> (ILspace/kscience/controls/client/DoocsAction;Ljava/lang/String;Lspace/kscience/controls/client/EqData;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
|
||||||
public fun <init> (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 <init> (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 <init> (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;)V
|
|
||||||
public synthetic fun <init> (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 <init> (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 <init> (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 <init> (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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
|||||||
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()
|
|
||||||
native()
|
|
||||||
wasm()
|
|
||||||
useCoroutines()
|
|
||||||
useSerialization {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
|
|
||||||
commonMain {
|
|
||||||
api(projects.magix.magixApi)
|
|
||||||
api(projects.controlsCore)
|
|
||||||
api(libs.uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmTest{
|
|
||||||
implementation(spclibs.logback.classic)
|
|
||||||
implementation(projects.magix.magixServer)
|
|
||||||
implementation(projects.magix.magixRsocket)
|
|
||||||
implementation(spclibs.ktor.server.cio)
|
|
||||||
implementation(spclibs.ktor.server.websockets)
|
|
||||||
implementation(spclibs.ktor.client.cio)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,273 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import space.kscience.controls.api.*
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.name
|
|
||||||
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.send
|
|
||||||
import space.kscience.magix.api.subscribe
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
private fun stringUID() = uuid4().leastSignificantBits.toString(16)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A remote accessible device that relies on connection via Magix
|
|
||||||
*/
|
|
||||||
public class DeviceClient internal constructor(
|
|
||||||
override val context: Context,
|
|
||||||
private val deviceName: Name,
|
|
||||||
propertyDescriptors: Collection<PropertyDescriptor>,
|
|
||||||
actionDescriptors: Collection<ActionDescriptor>,
|
|
||||||
incomingFlow: Flow<DeviceMessage>,
|
|
||||||
private val send: suspend (DeviceMessage) -> Unit,
|
|
||||||
) : CachingDevice {
|
|
||||||
|
|
||||||
|
|
||||||
override var actionDescriptors: Collection<ActionDescriptor> = actionDescriptors
|
|
||||||
internal set
|
|
||||||
|
|
||||||
override var propertyDescriptors: Collection<PropertyDescriptor> = propertyDescriptors
|
|
||||||
internal set
|
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext = context.coroutineContext + Job(context.coroutineContext[Job])
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
private val propertyCache = HashMap<String, Meta>()
|
|
||||||
|
|
||||||
private val flowInternal = incomingFlow.filter {
|
|
||||||
it.sourceDevice == deviceName
|
|
||||||
}.onEach { message ->
|
|
||||||
when (message) {
|
|
||||||
is PropertyChangedMessage -> mutex.withLock {
|
|
||||||
propertyCache[message.property] = message.value
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.shareIn(this, started = SharingStarted.Eagerly)
|
|
||||||
|
|
||||||
override val messageFlow: Flow<DeviceMessage> get() = flowInternal
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun readProperty(propertyName: String): Meta {
|
|
||||||
send(
|
|
||||||
PropertyGetMessage(propertyName, targetDevice = deviceName)
|
|
||||||
)
|
|
||||||
return messageFlow.filterIsInstance<PropertyChangedMessage>().first {
|
|
||||||
it.property == propertyName
|
|
||||||
}.value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperty(propertyName: String): Meta? = propertyCache[propertyName]
|
|
||||||
|
|
||||||
override suspend fun invalidate(propertyName: String) {
|
|
||||||
mutex.withLock {
|
|
||||||
propertyCache.remove(propertyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeProperty(propertyName: String, value: Meta) {
|
|
||||||
send(
|
|
||||||
PropertySetMessage(propertyName, value, targetDevice = deviceName)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
|
|
||||||
val id = stringUID()
|
|
||||||
send(
|
|
||||||
ActionExecuteMessage(actionName, argument, id, targetDevice = deviceName)
|
|
||||||
)
|
|
||||||
return messageFlow.filterIsInstance<ActionResultMessage>().first {
|
|
||||||
it.action == actionName && it.requestId == id
|
|
||||||
}.result
|
|
||||||
}
|
|
||||||
|
|
||||||
private val lifecycleStateFlow = messageFlow.filterIsInstance<DeviceLifeCycleMessage>()
|
|
||||||
.map { it.state }.stateIn(this, started = SharingStarted.Eagerly, LifecycleState.STARTED)
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
override val lifecycleState: LifecycleState get() = lifecycleStateFlow.value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a remote device via this endpoint.
|
|
||||||
*
|
|
||||||
* @param context a [Context] to run device in
|
|
||||||
* @param thisEndpoint the name of this endpoint
|
|
||||||
* @param deviceEndpoint the name of endpoint in Magix to connect to
|
|
||||||
* @param deviceName the name of device within endpoint
|
|
||||||
*/
|
|
||||||
public suspend fun MagixEndpoint.remoteDevice(
|
|
||||||
context: Context,
|
|
||||||
thisEndpoint: String,
|
|
||||||
deviceEndpoint: String,
|
|
||||||
deviceName: Name,
|
|
||||||
): DeviceClient = coroutineScope {
|
|
||||||
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(deviceEndpoint))
|
|
||||||
.map { it.second }
|
|
||||||
.filter {
|
|
||||||
it.sourceDevice == null || it.sourceDevice == deviceName
|
|
||||||
}
|
|
||||||
|
|
||||||
val deferredDescriptorMessage = CompletableDeferred<DescriptionMessage>()
|
|
||||||
|
|
||||||
launch {
|
|
||||||
deferredDescriptorMessage.complete(
|
|
||||||
subscription.filterIsInstance<DescriptionMessage>().first()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
send(
|
|
||||||
format = DeviceManager.magixFormat,
|
|
||||||
payload = GetDescriptionMessage(targetDevice = deviceName),
|
|
||||||
source = thisEndpoint,
|
|
||||||
target = deviceEndpoint,
|
|
||||||
id = stringUID()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
val descriptionMessage = deferredDescriptorMessage.await()
|
|
||||||
|
|
||||||
DeviceClient(
|
|
||||||
context = context,
|
|
||||||
deviceName = deviceName,
|
|
||||||
propertyDescriptors = descriptionMessage.properties,
|
|
||||||
actionDescriptors = descriptionMessage.actions,
|
|
||||||
incomingFlow = subscription
|
|
||||||
) {
|
|
||||||
send(
|
|
||||||
format = DeviceManager.magixFormat,
|
|
||||||
payload = it,
|
|
||||||
source = thisEndpoint,
|
|
||||||
target = deviceEndpoint,
|
|
||||||
id = stringUID()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a dynamic [DeviceHub] from incoming messages
|
|
||||||
*/
|
|
||||||
public suspend fun MagixEndpoint.remoteDeviceHub(
|
|
||||||
context: Context,
|
|
||||||
thisEndpoint: String,
|
|
||||||
deviceEndpoint: String,
|
|
||||||
): DeviceHub {
|
|
||||||
val devices = mutableMapOf<Name, DeviceClient>()
|
|
||||||
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(deviceEndpoint)).map { it.second }
|
|
||||||
subscription.filterIsInstance<DescriptionMessage>().onEach { descriptionMessage ->
|
|
||||||
devices.getOrPut(descriptionMessage.sourceDevice) {
|
|
||||||
DeviceClient(
|
|
||||||
context = context,
|
|
||||||
deviceName = descriptionMessage.sourceDevice,
|
|
||||||
propertyDescriptors = descriptionMessage.properties,
|
|
||||||
actionDescriptors = descriptionMessage.actions,
|
|
||||||
incomingFlow = subscription
|
|
||||||
) {
|
|
||||||
send(
|
|
||||||
format = DeviceManager.magixFormat,
|
|
||||||
payload = it,
|
|
||||||
source = thisEndpoint,
|
|
||||||
target = deviceEndpoint,
|
|
||||||
id = stringUID()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.run {
|
|
||||||
propertyDescriptors = descriptionMessage.properties
|
|
||||||
}
|
|
||||||
}.launchIn(context)
|
|
||||||
|
|
||||||
|
|
||||||
send(
|
|
||||||
format = DeviceManager.magixFormat,
|
|
||||||
payload = GetDescriptionMessage(targetDevice = null),
|
|
||||||
source = thisEndpoint,
|
|
||||||
target = deviceEndpoint,
|
|
||||||
id = stringUID()
|
|
||||||
)
|
|
||||||
|
|
||||||
return DeviceHub(devices)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a description update for all devices on an endpoint
|
|
||||||
*/
|
|
||||||
public suspend fun MagixEndpoint.requestDeviceUpdate(
|
|
||||||
thisEndpoint: String,
|
|
||||||
deviceEndpoint: String,
|
|
||||||
) {
|
|
||||||
send(
|
|
||||||
format = DeviceManager.magixFormat,
|
|
||||||
payload = GetDescriptionMessage(),
|
|
||||||
source = thisEndpoint,
|
|
||||||
target = deviceEndpoint,
|
|
||||||
id = stringUID()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe on specific property of a device without creating a device
|
|
||||||
*/
|
|
||||||
public fun <T> MagixEndpoint.controlsPropertyFlow(
|
|
||||||
endpointName: String,
|
|
||||||
deviceName: Name,
|
|
||||||
propertySpec: DevicePropertySpec<*, T>,
|
|
||||||
): Flow<T> {
|
|
||||||
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(endpointName)).map { it.second }
|
|
||||||
|
|
||||||
return subscription.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { message ->
|
|
||||||
message.sourceDevice == deviceName && message.property == propertySpec.name
|
|
||||||
}.map {
|
|
||||||
propertySpec.converter.read(it.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun <T> MagixEndpoint.sendControlsPropertyChange(
|
|
||||||
sourceEndpointName: String,
|
|
||||||
targetEndpointName: String,
|
|
||||||
deviceName: Name,
|
|
||||||
propertySpec: DevicePropertySpec<*, T>,
|
|
||||||
value: T,
|
|
||||||
) {
|
|
||||||
val message = PropertySetMessage(
|
|
||||||
property = propertySpec.name,
|
|
||||||
value = propertySpec.converter.convert(value),
|
|
||||||
targetDevice = deviceName
|
|
||||||
)
|
|
||||||
send(DeviceManager.magixFormat, message, source = sourceEndpointName, target = targetEndpointName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe on property change messages together with property values
|
|
||||||
*/
|
|
||||||
public fun <T> MagixEndpoint.controlsPropertyMessageFlow(
|
|
||||||
endpointName: String,
|
|
||||||
deviceName: Name,
|
|
||||||
propertySpec: DevicePropertySpec<*, T>,
|
|
||||||
): Flow<Pair<PropertyChangedMessage, T>> {
|
|
||||||
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(endpointName)).map { it.second }
|
|
||||||
|
|
||||||
return subscription.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { message ->
|
|
||||||
message.sourceDevice == deviceName && message.property == propertySpec.name
|
|
||||||
}.map {
|
|
||||||
it to propertySpec.converter.read(it.value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import space.kscience.controls.api.PropertyChangedMessage
|
|
||||||
import space.kscience.controls.api.getOrReadProperty
|
|
||||||
import space.kscience.controls.spec.DeviceActionSpec
|
|
||||||
import space.kscience.controls.spec.DevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.MutableDevicePropertySpec
|
|
||||||
import space.kscience.controls.spec.name
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An accessor that allows DeviceClient to connect to any property without type checks
|
|
||||||
*/
|
|
||||||
public suspend fun <T> DeviceClient.read(propertySpec: DevicePropertySpec<*, T>): T =
|
|
||||||
propertySpec.converter.readOrNull(readProperty(propertySpec.name)) ?: error("Property read result is not valid")
|
|
||||||
|
|
||||||
public suspend fun <T> DeviceClient.request(propertySpec: DevicePropertySpec<*, T>): T =
|
|
||||||
propertySpec.converter.read(getOrReadProperty(propertySpec.name))
|
|
||||||
|
|
||||||
public fun <T> DeviceClient.getCached(propertySpec: DevicePropertySpec<*, T>): T? =
|
|
||||||
getProperty(propertySpec.name)?.let { propertySpec.converter.read(it) }
|
|
||||||
|
|
||||||
|
|
||||||
public suspend fun <T> DeviceClient.write(propertySpec: MutableDevicePropertySpec<*, T>, value: T) {
|
|
||||||
writeProperty(propertySpec.name, propertySpec.converter.convert(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> DeviceClient.writeAsync(propertySpec: MutableDevicePropertySpec<*, T>, value: T): Job = launch {
|
|
||||||
write(propertySpec, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> DeviceClient.propertyFlow(spec: DevicePropertySpec<*, T>): Flow<T> = messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == spec.name }
|
|
||||||
.mapNotNull { spec.converter.readOrNull(it.value) }
|
|
||||||
|
|
||||||
public fun <T> DeviceClient.onPropertyChange(
|
|
||||||
spec: DevicePropertySpec<*, T>,
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
callback: suspend PropertyChangedMessage.(T) -> Unit,
|
|
||||||
): Job = messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == spec.name }
|
|
||||||
.onEach { change ->
|
|
||||||
val newValue = spec.converter.readOrNull(change.value)
|
|
||||||
if (newValue != null) {
|
|
||||||
change.callback(newValue)
|
|
||||||
}
|
|
||||||
}.launchIn(scope)
|
|
||||||
|
|
||||||
public fun <T> DeviceClient.useProperty(
|
|
||||||
spec: DevicePropertySpec<*, T>,
|
|
||||||
scope: CoroutineScope = this,
|
|
||||||
callback: suspend (T) -> Unit,
|
|
||||||
): Job = scope.launch {
|
|
||||||
callback(read(spec))
|
|
||||||
messageFlow
|
|
||||||
.filterIsInstance<PropertyChangedMessage>()
|
|
||||||
.filter { it.property == spec.name }
|
|
||||||
.collect { change ->
|
|
||||||
val newValue = spec.converter.readOrNull(change.value)
|
|
||||||
if (newValue != null) {
|
|
||||||
callback(newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun <I, O> DeviceClient.execute(actionSpec: DeviceActionSpec<*, I, O>, input: I): O {
|
|
||||||
val inputMeta = actionSpec.inputConverter.convert(input)
|
|
||||||
val res = execute(actionSpec.name, inputMeta)
|
|
||||||
return actionSpec.outputConverter.read(res ?: Meta.EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
public suspend fun <O> DeviceClient.execute(actionSpec: DeviceActionSpec<*, Unit, O>): O {
|
|
||||||
val res = execute(actionSpec.name, Meta.EMPTY)
|
|
||||||
return actionSpec.outputConverter.read(res ?: Meta.EMPTY)
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.catch
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import space.kscience.controls.api.DeviceMessage
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.controls.manager.hubMessageFlow
|
|
||||||
import space.kscience.controls.manager.respondHubMessage
|
|
||||||
import space.kscience.dataforge.context.error
|
|
||||||
import space.kscience.dataforge.context.logger
|
|
||||||
import space.kscience.magix.api.*
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
|
||||||
|
|
||||||
|
|
||||||
internal val controlsMagixFormat: MagixFormat<DeviceMessage> = MagixFormat(
|
|
||||||
DeviceMessage.serializer(),
|
|
||||||
setOf("controls-kt")
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A magix message format to work with Controls-kt data
|
|
||||||
*/
|
|
||||||
public val DeviceManager.Companion.magixFormat: MagixFormat<DeviceMessage> get() = controlsMagixFormat
|
|
||||||
|
|
||||||
internal fun generateId(request: MagixMessage): String = if (request.id != null) {
|
|
||||||
"${request.id}.response"
|
|
||||||
} else {
|
|
||||||
uuid4().leastSignificantBits.toULong().toString(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1)
|
|
||||||
*
|
|
||||||
* Accepts messages with target that equals [endpointID] or null (broadcast messages)
|
|
||||||
*/
|
|
||||||
public fun DeviceManager.launchMagixService(
|
|
||||||
endpoint: MagixEndpoint,
|
|
||||||
endpointID: String,
|
|
||||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
|
||||||
): Job = context.launch(coroutineContext) {
|
|
||||||
endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID, null)).onEach { (request, payload) ->
|
|
||||||
val responsePayload: List<DeviceMessage> = respondHubMessage(payload)
|
|
||||||
responsePayload.forEach {
|
|
||||||
endpoint.send(
|
|
||||||
format = controlsMagixFormat,
|
|
||||||
payload = it,
|
|
||||||
source = endpointID,
|
|
||||||
target = request.sourceEndpoint,
|
|
||||||
id = generateId(request),
|
|
||||||
parentId = request.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.catch { error ->
|
|
||||||
if (error !is CancellationException) logger.error(error) { "Error while responding to message: ${error.message}" }
|
|
||||||
}.launchIn(this)
|
|
||||||
|
|
||||||
hubMessageFlow().onEach { payload ->
|
|
||||||
endpoint.send(
|
|
||||||
format = controlsMagixFormat,
|
|
||||||
payload = payload,
|
|
||||||
source = endpointID,
|
|
||||||
id = "df[${payload.hashCode()}]"
|
|
||||||
)
|
|
||||||
}.catch { error ->
|
|
||||||
logger.error(error) { "Error while sending a message: ${error.message}" }
|
|
||||||
}.launchIn(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
"action":"get|set",
|
|
||||||
"eq_address": "string",
|
|
||||||
"eq_data": {
|
|
||||||
"type_id": "int[required]",
|
|
||||||
"type": "string[optional]",
|
|
||||||
"value": "object|value",
|
|
||||||
"event_id": "int[optional]",
|
|
||||||
"error": "int[optional]",
|
|
||||||
"time": "long[optional]",
|
|
||||||
"message": "string[optional]"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public enum class DoocsAction {
|
|
||||||
get,
|
|
||||||
set,
|
|
||||||
names
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public data class EqData(
|
|
||||||
@SerialName("type_id")
|
|
||||||
val typeId: Int,
|
|
||||||
val type: String? = null,
|
|
||||||
val value: Meta? = null,
|
|
||||||
@SerialName("event_id")
|
|
||||||
val eventId: Int? = null,
|
|
||||||
val error: Int? = null,
|
|
||||||
val time: Long? = null,
|
|
||||||
val message: String? = null
|
|
||||||
) {
|
|
||||||
public companion object {
|
|
||||||
internal const val DATA_NULL: Int = 0
|
|
||||||
internal const val DATA_INT: Int = 1
|
|
||||||
internal const val DATA_FLOAT: Int = 2
|
|
||||||
internal const val DATA_STRING: Int = 3
|
|
||||||
internal const val DATA_BOOL: Int = 4
|
|
||||||
internal const val DATA_STRING16: Int = 5
|
|
||||||
internal const val DATA_DOUBLE: Int = 6
|
|
||||||
internal const val DATA_TEXT: Int = 7
|
|
||||||
internal const val DATA_TDS: Int = 12
|
|
||||||
internal const val DATA_XY: Int = 13
|
|
||||||
internal const val DATA_IIII: Int = 14
|
|
||||||
internal const val DATA_IFFF: Int = 15
|
|
||||||
internal const val DATA_USTR: Int = 16
|
|
||||||
internal const val DATA_TTII: Int = 18
|
|
||||||
internal const val DATA_SPECTRUM: Int = 19
|
|
||||||
internal const val DATA_XML: Int = 20
|
|
||||||
internal const val DATA_XYZS: Int = 21
|
|
||||||
internal const val DATA_IMAGE: Int = 22
|
|
||||||
internal const val DATA_GSPECTRUM: Int = 24
|
|
||||||
internal const val DATA_SHORT: Int = 25
|
|
||||||
internal const val DATA_LONG: Int = 26
|
|
||||||
internal const val DATA_USHORT: Int = 27
|
|
||||||
internal const val DATA_UINT: Int = 28
|
|
||||||
internal const val DATA_ULONG: Int = 29
|
|
||||||
|
|
||||||
|
|
||||||
internal const val DATA_A_FLOAT: Int = 100
|
|
||||||
internal const val DATA_A_TDS: Int = 101
|
|
||||||
internal const val DATA_A_XY: Int = 102
|
|
||||||
internal const val DATA_A_USTR: Int = 103
|
|
||||||
internal const val DATA_A_INT: Int = 105
|
|
||||||
internal const val DATA_A_BYTE: Int = 106
|
|
||||||
internal const val DATA_A_XYZS: Int = 108
|
|
||||||
internal const val DATA_MDA_FLOAT: Int = 109
|
|
||||||
internal const val DATA_A_DOUBLE: Int = 110
|
|
||||||
internal const val DATA_A_BOOL: Int = 111
|
|
||||||
internal const val DATA_A_STRING: Int = 112
|
|
||||||
internal const val DATA_A_SHORT: Int = 113
|
|
||||||
internal const val DATA_A_LONG: Int = 114
|
|
||||||
internal const val DATA_MDA_DOUBLE: Int = 115
|
|
||||||
internal const val DATA_A_USHORT: Int = 116
|
|
||||||
internal const val DATA_A_UINT: Int = 117
|
|
||||||
internal const val DATA_A_ULONG: Int = 118
|
|
||||||
|
|
||||||
internal const val DATA_A_THUMBNAIL: Int = 120
|
|
||||||
|
|
||||||
internal const val DATA_A_TS_BOOL: Int = 1000
|
|
||||||
internal const val DATA_A_TS_INT: Int = 1001
|
|
||||||
internal const val DATA_A_TS_FLOAT: Int = 1002
|
|
||||||
internal const val DATA_A_TS_DOUBLE: Int = 1003
|
|
||||||
internal const val DATA_A_TS_LONG: Int = 1004
|
|
||||||
internal const val DATA_A_TS_STRING: Int = 1005
|
|
||||||
internal const val DATA_A_TS_USTR: Int = 1006
|
|
||||||
internal const val DATA_A_TS_XML: Int = 1007
|
|
||||||
internal const val DATA_A_TS_XY: Int = 1008
|
|
||||||
internal const val DATA_A_TS_IIII: Int = 1009
|
|
||||||
internal const val DATA_A_TS_IFFF: Int = 1010
|
|
||||||
internal const val DATA_A_TS_SPECTRUM: Int = 1013
|
|
||||||
internal const val DATA_A_TS_XYZS: Int = 1014
|
|
||||||
internal const val DATA_A_TS_GSPECTRUM: Int = 1015
|
|
||||||
|
|
||||||
internal const val DATA_KEYVAL: Int = 1016
|
|
||||||
|
|
||||||
internal const val DATA_A_TS_SHORT: Int = 1017
|
|
||||||
internal const val DATA_A_TS_USHORT: Int = 1018
|
|
||||||
internal const val DATA_A_TS_UINT: Int = 1019
|
|
||||||
internal const val DATA_A_TS_ULONG: Int = 1020
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public data class DoocsPayload(
|
|
||||||
val action: DoocsAction,
|
|
||||||
@SerialName("eq_address")
|
|
||||||
val address: String,
|
|
||||||
@SerialName("eq_data")
|
|
||||||
val data: EqData?
|
|
||||||
)
|
|
@ -1,155 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import space.kscience.controls.api.getOrReadProperty
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.dataforge.context.error
|
|
||||||
import space.kscience.dataforge.context.logger
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.names.get
|
|
||||||
import space.kscience.magix.api.*
|
|
||||||
|
|
||||||
public const val TANGO_MAGIX_FORMAT: String = "tango"
|
|
||||||
|
|
||||||
/*
|
|
||||||
See https://github.com/waltz-controls/rfc/tree/master/4 for details
|
|
||||||
|
|
||||||
"action":"read|write|exec|pipe",
|
|
||||||
"timestamp": "int",
|
|
||||||
"host":"tango_host",
|
|
||||||
"device":"device name",
|
|
||||||
"name":"attribute, command or pipe's name",
|
|
||||||
"[value]":"attribute's value",
|
|
||||||
"[quality]":"VALID|WARNING|ALARM",
|
|
||||||
"[argin]":"command argin",
|
|
||||||
"[argout]":"command argout",
|
|
||||||
"[data]":"pipe's data",
|
|
||||||
"[errors]":[]
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public enum class TangoAction {
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
exec,
|
|
||||||
pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public enum class TangoQuality {
|
|
||||||
VALID,
|
|
||||||
WARNING,
|
|
||||||
ALARM
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
public data class TangoPayload(
|
|
||||||
val action: TangoAction,
|
|
||||||
val timestamp: Int,
|
|
||||||
val host: String,
|
|
||||||
val device: String,
|
|
||||||
val name: String,
|
|
||||||
val value: Meta? = null,
|
|
||||||
val quality: TangoQuality = TangoQuality.VALID,
|
|
||||||
val argin: Meta? = null,
|
|
||||||
val argout: Meta? = null,
|
|
||||||
val data: Meta? = null,
|
|
||||||
val errors: List<String>? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
internal val tangoMagixFormat = MagixFormat(
|
|
||||||
TangoPayload.serializer(),
|
|
||||||
setOf("tango")
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls-kt device binding for Tango-flavored magix loop
|
|
||||||
*/
|
|
||||||
public fun DeviceManager.launchTangoMagix(
|
|
||||||
endpoint: MagixEndpoint,
|
|
||||||
endpointID: String = TANGO_MAGIX_FORMAT,
|
|
||||||
): Job {
|
|
||||||
|
|
||||||
suspend fun respond(request: MagixMessage, payload: TangoPayload, payloadBuilder: (TangoPayload) -> TangoPayload) {
|
|
||||||
endpoint.send(
|
|
||||||
tangoMagixFormat,
|
|
||||||
payload = payloadBuilder(payload),
|
|
||||||
source = endpointID,
|
|
||||||
id = generateId(request),
|
|
||||||
parentId = request.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return context.launch {
|
|
||||||
endpoint.subscribe(tangoMagixFormat).onEach { (request, payload) ->
|
|
||||||
try {
|
|
||||||
val device = devices[payload.device] ?: error("Device ${payload.device} not found")
|
|
||||||
when (payload.action) {
|
|
||||||
TangoAction.read -> {
|
|
||||||
val value = device.getOrReadProperty(payload.name)
|
|
||||||
respond(request, payload) { requestPayload ->
|
|
||||||
requestPayload.copy(
|
|
||||||
value = value,
|
|
||||||
quality = TangoQuality.VALID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TangoAction.write -> {
|
|
||||||
payload.value?.let { value ->
|
|
||||||
device.writeProperty(payload.name, value)
|
|
||||||
}
|
|
||||||
//wait for value to be written and return final state
|
|
||||||
val value = device.getOrReadProperty(payload.name)
|
|
||||||
respond(request, payload) { requestPayload ->
|
|
||||||
requestPayload.copy(
|
|
||||||
value = value,
|
|
||||||
quality = TangoQuality.VALID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TangoAction.exec -> {
|
|
||||||
val result = device.execute(payload.name, payload.argin)
|
|
||||||
respond(request, payload) { requestPayload ->
|
|
||||||
requestPayload.copy(
|
|
||||||
argout = result,
|
|
||||||
quality = TangoQuality.VALID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TangoAction.pipe -> TODO("Pipe not implemented")
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
logger.error(ex) { "Error while responding to message" }
|
|
||||||
endpoint.send(
|
|
||||||
tangoMagixFormat,
|
|
||||||
payload = payload.copy(quality = TangoQuality.WARNING),
|
|
||||||
source = endpointID,
|
|
||||||
id = generateId(request),
|
|
||||||
parentId = request.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.launchIn(this)
|
|
||||||
|
|
||||||
//TODO implement subscriptions?
|
|
||||||
// controller.messageOutput().onEach { payload ->
|
|
||||||
// endpoint.broadcast(
|
|
||||||
// MagixMessage(
|
|
||||||
// format = TANGO_MAGIX_FORMAT,
|
|
||||||
// id = "df[${payload.hashCode()}]",
|
|
||||||
// origin = endpointID,
|
|
||||||
// payload = payload
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }.catch { error ->
|
|
||||||
// logger.error(error) { "Error while sending a message" }
|
|
||||||
// }.launchIn(this)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.merge
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import space.kscience.controls.api.DeviceHub
|
|
||||||
import space.kscience.controls.api.DeviceMessage
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.controls.manager.hubMessageFlow
|
|
||||||
import space.kscience.controls.manager.install
|
|
||||||
import space.kscience.controls.manager.respondHubMessage
|
|
||||||
import space.kscience.controls.spec.*
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.context.request
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.get
|
|
||||||
import space.kscience.dataforge.meta.int
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import space.kscience.magix.api.MagixEndpoint
|
|
||||||
import space.kscience.magix.api.MagixMessage
|
|
||||||
import space.kscience.magix.api.MagixMessageFilter
|
|
||||||
import kotlin.random.Random
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertContains
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
class VirtualMagixEndpoint(val hub: DeviceHub) : MagixEndpoint {
|
|
||||||
|
|
||||||
private val additionalMessages = MutableSharedFlow<DeviceMessage>(1)
|
|
||||||
|
|
||||||
override fun subscribe(
|
|
||||||
filter: MagixMessageFilter,
|
|
||||||
): Flow<MagixMessage> = merge(hub.hubMessageFlow(), additionalMessages).map {
|
|
||||||
MagixMessage(
|
|
||||||
format = DeviceManager.magixFormat.defaultFormat,
|
|
||||||
payload = MagixEndpoint.magixJson.encodeToJsonElement(DeviceManager.magixFormat.serializer, it),
|
|
||||||
sourceEndpoint = "device",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun broadcast(message: MagixMessage) {
|
|
||||||
hub.respondHubMessage(
|
|
||||||
Json.decodeFromJsonElement(DeviceManager.magixFormat.serializer, message.payload)
|
|
||||||
).forEach {
|
|
||||||
additionalMessages.emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class RemoteDeviceConnect {
|
|
||||||
|
|
||||||
class TestDevice(context: Context, meta: Meta) : DeviceBySpec<TestDevice>(TestDevice, context, meta) {
|
|
||||||
private val rng = Random(meta["seed"].int ?: 0)
|
|
||||||
|
|
||||||
private val randomValue get() = rng.nextDouble()
|
|
||||||
|
|
||||||
companion object : DeviceSpec<TestDevice>(), Factory<TestDevice> {
|
|
||||||
|
|
||||||
override fun build(context: Context, meta: Meta): TestDevice = TestDevice(context, meta)
|
|
||||||
|
|
||||||
val value by doubleProperty { randomValue }
|
|
||||||
|
|
||||||
override suspend fun TestDevice.onOpen() {
|
|
||||||
doRecurring((meta["delay"].int ?: 10).milliseconds) {
|
|
||||||
read(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun deviceClient() = runTest {
|
|
||||||
val context = Context {
|
|
||||||
plugin(DeviceManager)
|
|
||||||
}
|
|
||||||
val deviceManager = context.request(DeviceManager)
|
|
||||||
|
|
||||||
deviceManager.install("test", TestDevice)
|
|
||||||
|
|
||||||
val virtualMagixEndpoint = VirtualMagixEndpoint(deviceManager)
|
|
||||||
|
|
||||||
val remoteDevice: DeviceClient = virtualMagixEndpoint.remoteDevice(context, "client", "device", "test".asName())
|
|
||||||
|
|
||||||
assertContains(0.0..1.0, remoteDevice.read(TestDevice.value))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun deviceHub() = runTest {
|
|
||||||
val context = Context {
|
|
||||||
plugin(DeviceManager)
|
|
||||||
}
|
|
||||||
val deviceManager = context.request(DeviceManager)
|
|
||||||
|
|
||||||
launch {
|
|
||||||
delay(50)
|
|
||||||
repeat(10) {
|
|
||||||
deviceManager.install("test[$it]", TestDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val virtualMagixEndpoint = VirtualMagixEndpoint(deviceManager)
|
|
||||||
|
|
||||||
val remoteHub = virtualMagixEndpoint.remoteDeviceHub(context, "client", "device")
|
|
||||||
|
|
||||||
assertEquals(0, remoteHub.devices.size)
|
|
||||||
|
|
||||||
delay(60)
|
|
||||||
//switch context to use actual delay
|
|
||||||
withContext(Dispatchers.Default) {
|
|
||||||
virtualMagixEndpoint.requestDeviceUpdate("client", "device")
|
|
||||||
delay(30)
|
|
||||||
assertEquals(10, remoteHub.devices.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package space.kscience.controls.client
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import space.kscience.controls.client.RemoteDeviceConnect.TestDevice
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.controls.manager.install
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.request
|
|
||||||
import space.kscience.magix.api.MagixEndpoint
|
|
||||||
import space.kscience.magix.rsocket.rSocketWithWebSockets
|
|
||||||
import space.kscience.magix.server.startMagixServer
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class MagixLoopTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun realDeviceHub() = runTest {
|
|
||||||
val context = Context {
|
|
||||||
coroutineContext(Dispatchers.Default)
|
|
||||||
plugin(DeviceManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
val server = context.startMagixServer()
|
|
||||||
|
|
||||||
val deviceManager = context.request(DeviceManager)
|
|
||||||
|
|
||||||
val deviceEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
|
|
||||||
|
|
||||||
deviceManager.launchMagixService(deviceEndpoint, "device")
|
|
||||||
|
|
||||||
val trigger = CompletableDeferred<Unit>()
|
|
||||||
|
|
||||||
context.launch {
|
|
||||||
repeat(10) {
|
|
||||||
deviceManager.install("test[$it]", TestDevice)
|
|
||||||
}
|
|
||||||
delay(100)
|
|
||||||
trigger.complete(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
|
|
||||||
|
|
||||||
val remoteHub = clientEndpoint.remoteDeviceHub(context, "client", "device")
|
|
||||||
|
|
||||||
assertEquals(0, remoteHub.devices.size)
|
|
||||||
clientEndpoint.requestDeviceUpdate("client", "device")
|
|
||||||
trigger.join()
|
|
||||||
assertEquals(10, remoteHub.devices.size)
|
|
||||||
server.stop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
# Module controls-modbus
|
|
||||||
|
|
||||||
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.4.0-dev-7`.
|
|
||||||
|
|
||||||
**Gradle Kotlin DSL:**
|
|
||||||
```kotlin
|
|
||||||
repositories {
|
|
||||||
maven("https://repo.kotlin.link")
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("space.kscience:controls-modbus:0.4.0-dev-7")
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,166 +0,0 @@
|
|||||||
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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> ()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
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
|||||||
import space.kscience.gradle.Maturity
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("space.kscience.gradle.mpp")
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
description = """
|
|
||||||
A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
kscience {
|
|
||||||
jvm()
|
|
||||||
jvmMain {
|
|
||||||
api(projects.controlsCore)
|
|
||||||
api(libs.j2mod)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,243 +0,0 @@
|
|||||||
package space.kscience.controls.modbus
|
|
||||||
|
|
||||||
import com.ghgande.j2mod.modbus.procimg.*
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.io.Buffer
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.ports.readShort
|
|
||||||
import space.kscience.controls.spec.*
|
|
||||||
import space.kscience.dataforge.io.Binary
|
|
||||||
|
|
||||||
|
|
||||||
public class DeviceProcessImageBuilder<D : Device> 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: MutableDevicePropertySpec<D, Boolean>,
|
|
||||||
): ObservableDigitalOut = bind(key) { coil ->
|
|
||||||
coil.addObserver { _, _ ->
|
|
||||||
device.writeAsync(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<D, Boolean>,
|
|
||||||
): 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<D, Short>,
|
|
||||||
): 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: MutableDevicePropertySpec<D, Short>,
|
|
||||||
): ObservableRegister = bind(key) { register ->
|
|
||||||
register.addObserver { _, _ ->
|
|
||||||
device.writeAsync(propertySpec, register.toShort())
|
|
||||||
}
|
|
||||||
device.useProperty(propertySpec) { value ->
|
|
||||||
register.setValue(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> bind(key: ModbusRegistryKey.InputRange<T>, propertySpec: DevicePropertySpec<D, T>) {
|
|
||||||
val registers = List(key.count) {
|
|
||||||
SimpleInputRegister()
|
|
||||||
}
|
|
||||||
|
|
||||||
registers.forEachIndexed { index, register ->
|
|
||||||
image.addInputRegister(key.address + index, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
device.useProperty(propertySpec) { value ->
|
|
||||||
val binary = Binary {
|
|
||||||
key.format.writeTo(this, value)
|
|
||||||
}
|
|
||||||
registers.forEachIndexed { index, register ->
|
|
||||||
register.setValue(binary.readShort(index * 2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger [block] if one of register changes.
|
|
||||||
*/
|
|
||||||
private fun List<ObservableRegister>.onChange(block: suspend (Buffer) -> Unit) {
|
|
||||||
var ready = false
|
|
||||||
|
|
||||||
forEach { register ->
|
|
||||||
register.addObserver { _, _ ->
|
|
||||||
ready = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
device.launch {
|
|
||||||
val builder = Buffer()
|
|
||||||
while (isActive) {
|
|
||||||
delay(1)
|
|
||||||
if (ready) {
|
|
||||||
val packet = builder.apply {
|
|
||||||
forEach { value ->
|
|
||||||
writeShort(value.toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block(packet)
|
|
||||||
ready = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T> bind(key: ModbusRegistryKey.HoldingRange<T>, propertySpec: MutableDevicePropertySpec<D, T>) {
|
|
||||||
val registers = List(key.count) {
|
|
||||||
ObservableRegister()
|
|
||||||
}
|
|
||||||
|
|
||||||
registers.forEachIndexed { index, register ->
|
|
||||||
image.addRegister(key.address + index, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
registers.onChange { packet ->
|
|
||||||
device.write(propertySpec, key.format.readFrom(packet))
|
|
||||||
}
|
|
||||||
|
|
||||||
device.useProperty(propertySpec) { value ->
|
|
||||||
val binary = Binary {
|
|
||||||
key.format.writeTo(this, value)
|
|
||||||
}
|
|
||||||
registers.forEachIndexed { index, observableRegister ->
|
|
||||||
observableRegister.setValue(binary.readShort(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 <T> bindAction(
|
|
||||||
key: ModbusRegistryKey.HoldingRange<T>,
|
|
||||||
action: suspend D.(T) -> Unit,
|
|
||||||
): List<ObservableRegister> {
|
|
||||||
val registers = List(key.count) {
|
|
||||||
ObservableRegister()
|
|
||||||
}
|
|
||||||
|
|
||||||
registers.forEachIndexed { index, register ->
|
|
||||||
image.addRegister(key.address + index, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
registers.onChange { packet ->
|
|
||||||
device.launch {
|
|
||||||
device.action(key.format.readFrom(packet))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return registers
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind the device to Modbus slave (server) image.
|
|
||||||
*/
|
|
||||||
public fun <D : Device> D.bindProcessImage(
|
|
||||||
unitId: Int = 0,
|
|
||||||
openOnBind: Boolean = true,
|
|
||||||
binding: DeviceProcessImageBuilder<D>.() -> Unit,
|
|
||||||
): ProcessImage {
|
|
||||||
val image = SimpleProcessImage(unitId)
|
|
||||||
DeviceProcessImageBuilder(this, image).apply(binding)
|
|
||||||
image.setLocked(true)
|
|
||||||
if (openOnBind) {
|
|
||||||
launch {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return image
|
|
||||||
}
|
|
@ -1,211 +0,0 @@
|
|||||||
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.SimpleInputRegister
|
|
||||||
import com.ghgande.j2mod.modbus.util.BitVector
|
|
||||||
import kotlinx.io.Buffer
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.dataforge.io.Buffer
|
|
||||||
import space.kscience.dataforge.io.ByteArray
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Modbus device backed by j2mod client
|
|
||||||
*/
|
|
||||||
public interface ModbusDevice : Device {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit id for this specific device
|
|
||||||
*/
|
|
||||||
public val unitId: Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 <T> ModbusRegistryKey.InputRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
|
||||||
val packet = readInputRegistersToPacket(address, count)
|
|
||||||
return format.readFrom(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 <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
|
||||||
val packet = readHoldingRegistersToPacket(address, count)
|
|
||||||
return format.readFrom(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.setValue(
|
|
||||||
thisRef: Any?,
|
|
||||||
property: KProperty<*>,
|
|
||||||
value: T,
|
|
||||||
) {
|
|
||||||
val buffer = ByteArray {
|
|
||||||
format.writeTo(this, value)
|
|
||||||
}
|
|
||||||
writeHoldingRegisters(address, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read multiple sequential modbus coils (bit-values)
|
|
||||||
*/
|
|
||||||
public fun ModbusDevice.readCoils(address: Int, count: Int): BitVector =
|
|
||||||
master.readCoils(unitId, address, count)
|
|
||||||
|
|
||||||
public fun ModbusDevice.readCoil(address: Int): Boolean =
|
|
||||||
master.readCoils(unitId, address, 1).getBit(0)
|
|
||||||
|
|
||||||
public fun ModbusDevice.writeCoils(address: Int, values: BooleanArray) {
|
|
||||||
val bitVector = BitVector(values.size)
|
|
||||||
values.forEachIndexed { index, value ->
|
|
||||||
bitVector.setBit(index, value)
|
|
||||||
}
|
|
||||||
master.writeMultipleCoils(unitId, address, bitVector)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusDevice.writeCoil(address: Int, value: Boolean) {
|
|
||||||
master.writeCoil(unitId, address, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusDevice.writeCoil(key: ModbusRegistryKey.Coil, value: Boolean) {
|
|
||||||
master.writeCoil(unitId, key.address, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusDevice.readInputDiscretes(address: Int, count: Int): BitVector =
|
|
||||||
master.readInputDiscretes(unitId, address, count)
|
|
||||||
|
|
||||||
public fun ModbusDevice.readInputDiscrete(address: Int): Boolean =
|
|
||||||
master.readInputDiscretes(unitId, address, 1).getBit(0)
|
|
||||||
|
|
||||||
public fun ModbusDevice.readInputRegisters(address: Int, count: Int): List<InputRegister> =
|
|
||||||
master.readInputRegisters(unitId, address, count).toList()
|
|
||||||
|
|
||||||
private fun Array<out InputRegister>.toBuffer(): ByteBuffer {
|
|
||||||
val buffer: ByteBuffer = ByteBuffer.allocate(size * 2)
|
|
||||||
forEachIndexed { index, value ->
|
|
||||||
buffer.position(index * 2)
|
|
||||||
buffer.put(value.toBytes())
|
|
||||||
}
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Array<out InputRegister>.toPacket(): Buffer = Buffer {
|
|
||||||
forEach { value ->
|
|
||||||
writeShort(value.toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
|
||||||
master.readInputRegisters(unitId, address, count).toBuffer()
|
|
||||||
|
|
||||||
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): Buffer =
|
|
||||||
master.readInputRegisters(unitId, 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<Register> =
|
|
||||||
master.readMultipleRegisters(unitId, 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(unitId, address, count).toBuffer()
|
|
||||||
|
|
||||||
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): Buffer =
|
|
||||||
master.readMultipleRegisters(unitId, 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(
|
|
||||||
unitId,
|
|
||||||
address,
|
|
||||||
Array<Register>(values.size) { SimpleInputRegister(values[it].toInt()) }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun ModbusDevice.writeHoldingRegister(address: Int, value: Short): Int =
|
|
||||||
master.writeSingleRegister(
|
|
||||||
unitId,
|
|
||||||
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.writeHoldingRegisters(address: Int, byteArray: ByteArray): Int {
|
|
||||||
val buffer = ByteBuffer.wrap(byteArray)
|
|
||||||
val array: ShortArray = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) }
|
|
||||||
|
|
||||||
return writeHoldingRegisters(address, array)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusDevice.modbusRegister(
|
|
||||||
address: Int,
|
|
||||||
): ReadWriteProperty<ModbusDevice, Short> = object : ReadWriteProperty<ModbusDevice, Short> {
|
|
||||||
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<ModbusDevice, Double> = object : ReadWriteProperty<ModbusDevice, Double> {
|
|
||||||
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(address, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package space.kscience.controls.modbus
|
|
||||||
|
|
||||||
import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.DeviceHub
|
|
||||||
import space.kscience.controls.spec.DeviceBySpec
|
|
||||||
import space.kscience.controls.spec.DeviceSpec
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of [DeviceBySpec] that includes Modbus RTU/TCP/UDP client
|
|
||||||
*/
|
|
||||||
public open class ModbusDeviceBySpec<D: Device>(
|
|
||||||
context: Context,
|
|
||||||
spec: DeviceSpec<D>,
|
|
||||||
override val unitId: Int,
|
|
||||||
override val master: AbstractModbusMaster,
|
|
||||||
private val disposeMasterOnClose: Boolean = true,
|
|
||||||
meta: Meta = Meta.EMPTY,
|
|
||||||
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta){
|
|
||||||
override suspend fun onStart() {
|
|
||||||
master.connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onStop() {
|
|
||||||
if(disposeMasterOnClose){
|
|
||||||
master.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class ModbusHub(
|
|
||||||
public val context: Context,
|
|
||||||
public val masterBuilder: () -> AbstractModbusMaster,
|
|
||||||
public val specs: Map<Name, Pair<Int, DeviceSpec<*>>>,
|
|
||||||
) : DeviceHub, AutoCloseable {
|
|
||||||
|
|
||||||
public val master: AbstractModbusMaster by lazy(masterBuilder)
|
|
||||||
|
|
||||||
override val devices: Map<Name, ModbusDevice> by lazy {
|
|
||||||
specs.mapValues { (_, pair) ->
|
|
||||||
ModbusDeviceBySpec(
|
|
||||||
context,
|
|
||||||
pair.second,
|
|
||||||
pair.first,
|
|
||||||
master
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
master.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,228 +0,0 @@
|
|||||||
package space.kscience.controls.modbus
|
|
||||||
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import space.kscience.dataforge.io.IOFormat
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modbus registry key
|
|
||||||
*/
|
|
||||||
public sealed class ModbusRegistryKey {
|
|
||||||
public abstract val address: Int
|
|
||||||
public open val count: Int = 1
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read-only boolean value
|
|
||||||
*/
|
|
||||||
public data class Coil(override val address: Int) : ModbusRegistryKey()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read-write boolean value
|
|
||||||
*/
|
|
||||||
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)"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A range of read-only register encoding a single value
|
|
||||||
*/
|
|
||||||
public class InputRange<T>(
|
|
||||||
address: Int,
|
|
||||||
override val count: Int,
|
|
||||||
public val format: IOFormat<T>,
|
|
||||||
) : InputRegister(address) {
|
|
||||||
public val endAddress: Int get() = address + count
|
|
||||||
override fun toString(): String = "InputRange(count=$count, format=$format)"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single read-write register
|
|
||||||
*/
|
|
||||||
public open class HoldingRegister(override val address: Int) : ModbusRegistryKey() {
|
|
||||||
override fun toString(): String = "HoldingRegister(address=$address)"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A range of read-write registers encoding a single value
|
|
||||||
*/
|
|
||||||
public class HoldingRange<T>(
|
|
||||||
address: Int,
|
|
||||||
override val count: Int,
|
|
||||||
public val format: IOFormat<T>,
|
|
||||||
) : HoldingRegister(address) {
|
|
||||||
public val endAddress: Int get() = address + count
|
|
||||||
override fun toString(): String = "HoldingRange(count=$count, format=$format)"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A base class for modbus registers
|
|
||||||
*/
|
|
||||||
public abstract class ModbusRegistryMap {
|
|
||||||
|
|
||||||
private val _entries: MutableMap<ModbusRegistryKey, String> = mutableMapOf<ModbusRegistryKey, String>()
|
|
||||||
|
|
||||||
public val entries: Map<ModbusRegistryKey, String> get() = _entries
|
|
||||||
|
|
||||||
protected fun <T : ModbusRegistryKey> register(key: T, description: String): T {
|
|
||||||
_entries[key] = description
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.Coil] key and return it
|
|
||||||
*/
|
|
||||||
protected fun coil(address: Int, description: String = ""): ModbusRegistryKey.Coil =
|
|
||||||
register(ModbusRegistryKey.Coil(address), description)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.DiscreteInput] key and return it
|
|
||||||
*/
|
|
||||||
protected fun discrete(address: Int, description: String = ""): ModbusRegistryKey.DiscreteInput =
|
|
||||||
register(ModbusRegistryKey.DiscreteInput(address), description)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.InputRegister] key and return it
|
|
||||||
*/
|
|
||||||
protected fun input(address: Int, description: String = ""): ModbusRegistryKey.InputRegister =
|
|
||||||
register(ModbusRegistryKey.InputRegister(address), description)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.InputRange] key and return it
|
|
||||||
*/
|
|
||||||
protected fun <T> input(
|
|
||||||
address: Int,
|
|
||||||
count: Int,
|
|
||||||
reader: IOFormat<T>,
|
|
||||||
description: String = "",
|
|
||||||
): ModbusRegistryKey.InputRange<T> = register(ModbusRegistryKey.InputRange(address, count, reader), description)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.HoldingRegister] key and return it
|
|
||||||
*/
|
|
||||||
protected fun register(address: Int, description: String = ""): ModbusRegistryKey.HoldingRegister =
|
|
||||||
register(ModbusRegistryKey.HoldingRegister(address), description)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.HoldingRange] key and return it
|
|
||||||
*/
|
|
||||||
protected fun <T> register(
|
|
||||||
address: Int,
|
|
||||||
count: Int,
|
|
||||||
format: IOFormat<T>,
|
|
||||||
description: String = "",
|
|
||||||
): ModbusRegistryKey.HoldingRange<T> = register(ModbusRegistryKey.HoldingRange(address, count, format), description)
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the register map. Throw an error if the map is invalid
|
|
||||||
*/
|
|
||||||
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 ModbusRegistryMap.print(to: Appendable = System.out) {
|
|
||||||
ModbusRegistryMap.validate(this)
|
|
||||||
entries.entries
|
|
||||||
.sortedWith(
|
|
||||||
Comparator.comparingInt<Map.Entry<ModbusRegistryKey, String>?> { 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 - 1}"
|
|
||||||
}
|
|
||||||
to.appendLine("${typeString}\t$rangeString\t$description")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusRegistryMap.toJson(): JsonArray = buildJsonArray {
|
|
||||||
ModbusRegistryMap.validate(this@toJson)
|
|
||||||
entries.forEach { (key, description) ->
|
|
||||||
|
|
||||||
val entry = buildJsonObject {
|
|
||||||
put(
|
|
||||||
"type",
|
|
||||||
when (key) {
|
|
||||||
is ModbusRegistryKey.Coil -> "Coil"
|
|
||||||
is ModbusRegistryKey.DiscreteInput -> "Discrete"
|
|
||||||
is ModbusRegistryKey.HoldingRegister -> "Register"
|
|
||||||
is ModbusRegistryKey.InputRegister -> "Input"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
put("address", key.address)
|
|
||||||
if (key.count > 1) {
|
|
||||||
put("count", key.count)
|
|
||||||
}
|
|
||||||
put("description", description)
|
|
||||||
}
|
|
||||||
|
|
||||||
add(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
# 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.4.0-dev-7`.
|
|
||||||
|
|
||||||
**Gradle Kotlin DSL:**
|
|
||||||
```kotlin
|
|
||||||
repositories {
|
|
||||||
maven("https://repo.kotlin.link")
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("space.kscience:controls-opcua:0.4.0-dev-7")
|
|
||||||
}
|
|
||||||
```
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user