Compare commits
195 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5b655a9354 | ||
61af2780ed | |||
990e4794a1 | |||
641daec7e9 | |||
a9f29f92ca | |||
82dbb3a1f0 | |||
ce03540b37 | |||
583fe5b54c | |||
69617e73b4 | |||
fa4f578a64 | |||
a172da0a3d | |||
dc2d0094fc | |||
d3d8413837 | |||
f9e20f8766 | |||
cf70339a9f | |||
870cb7ef40 | |||
fcabd9aed4 | |||
405bcd6ba3 | |||
6f5270ee37 | |||
c28944e10f | |||
4d4a9fba1c | |||
d1e9b0a5a5 | |||
d0d8869a1e | |||
20951a0b0f | |||
33e1aafa01 | |||
bfeeed00d5 | |||
166cc03fe2 | |||
2aa26ea802 | |||
76cec0469f | |||
2191bb7a77 | |||
9c39f44897 | |||
22359a5570 | |||
ced42779be | |||
ab1a478867 | |||
7b792afd7a | |||
25d7fe0a8e | |||
b7be570271 | |||
2a966d8cb3 | |||
0612c6e3a2 | |||
b8a82feed0 | |||
e7d02be849 | |||
a5a2edc81a | |||
28cb9af267 | |||
a6bf9b8db6 | |||
|
64e240f6c2 | ||
5e91ec9a97 | |||
1d7403ef30 | |||
aabf2b85a4 | |||
7103786ec9 | |||
53e506893f | |||
4c33c16c94 | |||
b66e23cca6 | |||
691b2ae67a | |||
86273101b6 | |||
fd1194205c | |||
96305b1be6 | |||
8cd191bb0d | |||
e7cfb1d2ba | |||
197675fc15 | |||
|
631eb73d23 | ||
00c66da847 | |||
ba84553be5 | |||
5b16b07172 | |||
3ef471d49e | |||
fca718cfc4 | |||
2a386568f9 | |||
fe24c0ee31 | |||
68805d6f42 | |||
a0fd8913eb | |||
dc6847196d | |||
619c2c280d | |||
4a09e66c42 | |||
c22902cc91 | |||
bd29789545 | |||
20345f846b | |||
535e44286c | |||
cfaeb964e7 | |||
025a444db8 | |||
879073e339 | |||
07adf143cf | |||
eb7507191e | |||
b6f3769529 | |||
ba4d2abd27 | |||
3d0b888c11 | |||
2e7eefeba9 | |||
427ecbf91a | |||
786e1637b4 | |||
|
0c4c2e4cc0 | ||
|
23e821d2c2 | ||
|
21c13ce3af | ||
|
cbdd6a477b | ||
|
89bb132e36 | ||
|
3ebbec35fe | ||
|
fe1575c5ee | ||
|
f977aca237 | ||
|
0c7cd951aa | ||
798c8eb4ef | |||
29c0291fa3 | |||
|
7ba36f2eb1 | ||
|
476dbfdeaf | ||
|
0c4d2fc9e1 | ||
|
dec45b6050 | ||
cdd1286365 | |||
76846a50cd | |||
|
f880b2d637 | ||
|
e07780e7da | ||
|
b79ad40a9b | ||
|
cfd7bce2f3 | ||
|
e793cdefee | ||
|
f77f6b7211 | ||
5da1559882 | |||
|
312cd06706 | ||
|
3639783b4e | ||
|
0b14c7ed7f | ||
85f6b81334 | |||
|
099649f090 | ||
|
53f9af3ba4 | ||
|
1d8cc33c91 | ||
|
a1c3902b92 | ||
|
81a1afa2b7 | ||
|
817d32a7e9 | ||
df6defd637 | |||
|
039e0d083b | ||
|
dcd496660c | ||
|
289ff6ffd0 | ||
4dc33c012d | |||
|
c1a1b6e696 | ||
|
357fcec4fe | ||
|
14015178ab | ||
|
bd23ca1129 | ||
|
24c5188c30 | ||
|
77496aedec | ||
7b56436983 | |||
ed2a2a29af | |||
|
418a97f99a | ||
|
a20fb97c02 | ||
1b021ca5ca | |||
|
b1d9d8e8ba | ||
|
7c4d0a5f36 | ||
|
5322bf743b | ||
7ae75d8bd4 | |||
|
ded8790f39 | ||
b3898d2ab3 | |||
553d819c54 | |||
3aa00ec491 | |||
d503f0499e | |||
3606bc3a46 | |||
b068403429 | |||
64d3f04469 | |||
1cf7058778 | |||
d0f22eec93 | |||
6e01e28015 | |||
89190db653 | |||
596e3a0cfc | |||
fe3958fd08 | |||
e182af403f | |||
b1d3ba59bc | |||
a87b46cd2b | |||
28e6e24cf7 | |||
3cbabd5d4b | |||
5e9cbab94d | |||
d79d345a44 | |||
87440f688f | |||
a510a8aedf | |||
a093f1921e | |||
6e4bf51a6f | |||
e3ea2304ef | |||
bf51768fe1 | |||
cb22374da5 | |||
b883bece48 | |||
d716eac07f | |||
2b4503c2fa | |||
65acfe824b | |||
|
319665e330 | ||
93c82db08e | |||
d68f5a9840 | |||
17beb29217 | |||
7fc27c49a3 | |||
92ab801967 | |||
356f3e15a6 | |||
dcf08d4426 | |||
78ee05371b | |||
f3cfe9c6db | |||
5c90e8e07b | |||
32c29240d2 | |||
f0acbbb8cc | |||
e5883dc318 | |||
8c8d53b187 | |||
599d08b62a | |||
62dc6ef127 | |||
23c66d9703 | |||
eb3121aed4 | |||
4b9f535002 | |||
64043cf2c0 | |||
dbf0466c64 |
24
.github/workflows/build.yml
vendored
Normal file
24
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
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
17
.github/workflows/gradle.yml
vendored
@ -1,17 +0,0 @@
|
||||
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
Normal file
31
.github/workflows/pages.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
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
Normal file
50
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
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,11 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
.idea/
|
||||
.gradle
|
||||
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
out/
|
||||
build/
|
||||
!gradle-wrapper.jar
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "magix/rfc"]
|
||||
path = magix/rfc
|
||||
url = https://github.com/waltz-controls/rfc
|
45
.space.kts
Normal file
45
.space.kts
Normal file
@ -0,0 +1,45 @@
|
||||
import kotlin.io.path.readText
|
||||
|
||||
job("Build") {
|
||||
gradlew("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3", "build")
|
||||
}
|
||||
|
||||
job("Publish") {
|
||||
startOn {
|
||||
gitPush { enabled = false }
|
||||
}
|
||||
container("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3") {
|
||||
env["SPACE_USER"] = "{{ project:space_user }}"
|
||||
env["SPACE_TOKEN"] = "{{ project:space_token }}"
|
||||
kotlinScript { api ->
|
||||
|
||||
val spaceUser = System.getenv("SPACE_USER")
|
||||
val spaceToken = System.getenv("SPACE_TOKEN")
|
||||
|
||||
// write the version to the build directory
|
||||
api.gradlew("version")
|
||||
|
||||
//read the version from build file
|
||||
val version = java.nio.file.Path.of("build/project-version.txt").readText()
|
||||
|
||||
val revisionSuffix = if (version.endsWith("SNAPSHOT")) {
|
||||
"-" + api.gitRevision().take(7)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
api.space().projects.automation.deployments.start(
|
||||
project = api.projectIdentifier(),
|
||||
targetIdentifier = TargetIdentifier.Key("maps-kt"),
|
||||
version = version+revisionSuffix,
|
||||
// automatically update deployment status based on the status of a job
|
||||
syncWithAutomationJob = true
|
||||
)
|
||||
api.gradlew(
|
||||
"publishAllPublicationsToSpaceRepository",
|
||||
"-Ppublishing.space.user=\"$spaceUser\"",
|
||||
"-Ppublishing.space.token=\"$spaceToken\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
1
.space/CODEOWNERS
Normal file
1
.space/CODEOWNERS
Normal file
@ -0,0 +1 @@
|
||||
./space/* "Project Admin"
|
32
CHANGELOG.md
Normal file
32
CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
- Core interfaces for building a device server
|
||||
- Magix service for binding controls devices (both as RPC client and server)
|
||||
- A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols
|
||||
- A client and server connectors for OPC-UA via Eclipse Milo
|
||||
- Implementation of byte ports on top os ktor-io asynchronous API
|
||||
- Implementation of direct serial port communication with JSerialComm
|
||||
- A combined Magix event loop server with web server for visualization.
|
||||
- An API for stand-alone Controls-kt device or a hub.
|
||||
- An implementation of controls-storage on top of JetBrains Xodus.
|
||||
- A kotlin API for magix standard and some zero-dependency magix services
|
||||
- Java API to work with magix endpoints without Kotlin
|
||||
- MQTT client magix endpoint
|
||||
- RabbitMQ client magix endpoint
|
||||
- Magix endpoint (client) based on RSocket
|
||||
- A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket routes.
|
||||
- Magix history database API
|
||||
- ZMQ client endpoint for Magix
|
||||
|
||||
### Changed
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Security
|
199
README.md
199
README.md
@ -1,25 +1,28 @@
|
||||
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||
|
||||
# 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
|
||||
of a slow control system, including a demo.
|
||||
|
||||
DataForge-control uses some concepts and modules of DataForge,
|
||||
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).
|
||||
Controls.kt uses some concepts and modules of DataForge,
|
||||
such as `Meta` (tree-like value structure).
|
||||
|
||||
To learn more about DataForge, please consult the following URLs:
|
||||
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
|
||||
* [DataForge documentation](http://npm.mipt.ru/dataforge/)
|
||||
* [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/)
|
||||
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
|
||||
* [DataForge documentation](http://npm.mipt.ru/dataforge/)
|
||||
* [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/)
|
||||
|
||||
DataForge-control is a [Kotlin-multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html)
|
||||
application. Asynchronous operations are implemented with
|
||||
[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
|
||||
Among other things, you can:
|
||||
@ -28,19 +31,173 @@ Among other things, you can:
|
||||
- Property values can be cached in the system and requested from devices as needed, asynchronously.
|
||||
- Connect devices to event bus via bidirectional message flows.
|
||||
|
||||
### `dataforge-control-core` module packages
|
||||
Example view of a demo:
|
||||
|
||||
- `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).
|
||||
![](docs/pictures/demo-view.png)
|
||||
|
||||
- `base` - contains baseline `Device` implementation
|
||||
[`DeviceBase`](dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt)
|
||||
and property implementation, including property asynchronous flows.
|
||||
## Documentation
|
||||
|
||||
* [Creating a device](docs/Device%20and%20DeviceSpec.md)
|
||||
|
||||
## Modules
|
||||
|
||||
|
||||
### [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-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-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
|
||||
|
||||
### [demo](demo)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [magix](magix)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [controls-storage/controls-xodus](controls-storage/controls-xodus)
|
||||
> An implementation of controls-storage on top of JetBrains Xodus.
|
||||
>
|
||||
> **Maturity**: PROTOTYPE
|
||||
|
||||
### [demo/all-things](demo/all-things)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [demo/car](demo/car)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [demo/echo](demo/echo)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [demo/magix-demo](demo/magix-demo)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [demo/many-devices](demo/many-devices)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [demo/mks-pdr900](demo/mks-pdr900)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [demo/motors](demo/motors)
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [magix/magix-api](magix/magix-api)
|
||||
> A kotlin API for magix standard and some zero-dependency magix services
|
||||
>
|
||||
> **Maturity**: EXPERIMENTAL
|
||||
|
||||
### [magix/magix-java-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-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
|
||||
|
||||
@ -49,7 +206,3 @@ the current time. The device is configurable via a simple TornadoFX-based contro
|
||||
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.
|
||||
|
||||
Example view of a demo:
|
||||
|
||||
![](docs/pictures/demo-view.png)
|
||||
|
@ -1,30 +1,37 @@
|
||||
import space.kscience.gradle.isInDevelopment
|
||||
import space.kscience.gradle.useApache2Licence
|
||||
import space.kscience.gradle.useSPCTeam
|
||||
|
||||
plugins {
|
||||
id("ru.mipt.npm.project")
|
||||
kotlin("jvm") apply false
|
||||
kotlin("js") apply false
|
||||
id("space.kscience.gradle.project")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by extra("0.2.0-dev-3")
|
||||
val ktorVersion: String by extra("1.4.1")
|
||||
val dataforgeVersion: String by extra("0.6.2-dev-3")
|
||||
val ktorVersion: String by extra(space.kscience.gradle.KScienceVersions.ktorVersion)
|
||||
val rsocketVersion by extra("0.15.4")
|
||||
val xodusVersion by extra("2.0.1")
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
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/")
|
||||
group = "space.kscience"
|
||||
version = "0.2.0"
|
||||
repositories{
|
||||
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
}
|
||||
|
||||
group = "hep.dataforge"
|
||||
version = "0.0.1"
|
||||
}
|
||||
|
||||
ksciencePublish {
|
||||
githubProject = "dataforge-control"
|
||||
bintrayRepo = "dataforge"
|
||||
pom("https://github.com/SciProgCentre/controls.kt") {
|
||||
useApache2Licence()
|
||||
useSPCTeam()
|
||||
}
|
||||
github("controls.kt", "SciProgCentre")
|
||||
space(
|
||||
if (isInDevelopment) {
|
||||
"https://maven.pkg.jetbrains.space/spc/p/sci/dev"
|
||||
} else {
|
||||
"https://maven.pkg.jetbrains.space/spc/p/sci/maven"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
apiValidation {
|
||||
validationDisabled = true
|
||||
}
|
||||
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
|
33
controls-core/README.md
Normal file
33
controls-core/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Module controls-core
|
||||
|
||||
Core interfaces for building a device server
|
||||
|
||||
## Features
|
||||
|
||||
- [device](src/commonMain/kotlin/space/kscience/controls/api/Device.kt) : Device API with subscription (asynchronous and pseudo-synchronous properties)
|
||||
- [deviceMessage](src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt) : Specification for messages used to communicate between Controls-kt devices.
|
||||
- [deviceHub](src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt) : Grouping of devices into local tree-like hubs.
|
||||
- [deviceSpec](src/commonMain/kotlin/space/kscience/controls/spec) : Mechanics and type-safe builders for devices. Including separation of device specification and device state.
|
||||
- [deviceManager](src/commonMain/kotlin/space/kscience/controls/manager) : DataForge DI integration for devices. Includes device builders.
|
||||
- [ports](src/commonMain/kotlin/space/kscience/controls/ports) : Working with asynchronous data sending and receiving raw byte arrays
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-core:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-core:0.2.0")
|
||||
}
|
||||
```
|
915
controls-core/api/controls-core.api
Normal file
915
controls-core/api/controls-core.api
Normal file
@ -0,0 +1,915 @@
|
||||
public final class space/kscience/controls/api/ActionDescriptor {
|
||||
public static final field Companion Lspace/kscience/controls/api/ActionDescriptor$Companion;
|
||||
public synthetic fun <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;
|
||||
}
|
||||
|
68
controls-core/build.gradle.kts
Normal file
68
controls-core/build.gradle.kts
Normal file
@ -0,0 +1,68 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
Core interfaces for building a device server
|
||||
""".trimIndent()
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
|
||||
kscience {
|
||||
jvm()
|
||||
js()
|
||||
native()
|
||||
useCoroutines()
|
||||
useSerialization{
|
||||
json()
|
||||
}
|
||||
useContextReceivers()
|
||||
dependencies {
|
||||
api("space.kscience:dataforge-io:$dataforgeVersion")
|
||||
api(spclibs.kotlinx.datetime)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package space.kscience.controls.api
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import space.kscience.controls.api.Device.Companion.DEVICE_TARGET
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
|
||||
/**
|
||||
* A lifecycle state of a device
|
||||
*/
|
||||
public enum class DeviceLifecycleState{
|
||||
INIT,
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
/**
|
||||
* General interface describing a managed Device.
|
||||
* [Device] is a supervisor scope encompassing all operations on a device.
|
||||
* When canceled, cancels all running processes.
|
||||
*/
|
||||
@Type(DEVICE_TARGET)
|
||||
public interface Device : AutoCloseable, ContextAware, 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
|
||||
|
||||
/**
|
||||
* Get the 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)
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public suspend fun open(): Unit = Unit
|
||||
|
||||
/**
|
||||
* Close and terminate the device. This function does not wait for the device to be closed.
|
||||
*/
|
||||
override fun close() {
|
||||
logger.info { "Device $this is closed" }
|
||||
cancel("The device is closed")
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public val lifecycleState: DeviceLifecycleState
|
||||
|
||||
public companion object {
|
||||
public const val DEVICE_TARGET: String = "device"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logical state of property or suspend to read the physical value.
|
||||
*/
|
||||
public suspend fun Device.getOrReadProperty(propertyName: String): Meta =
|
||||
getProperty(propertyName) ?: readProperty(propertyName)
|
||||
|
||||
/**
|
||||
* Get a snapshot of the device logical state
|
||||
*
|
||||
*/
|
||||
public fun Device.getAllProperties(): Meta = Meta {
|
||||
for (descriptor in propertyDescriptors) {
|
||||
setMeta(Name.parse(descriptor.name), getProperty(descriptor.name))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe on property changes for the whole device
|
||||
*/
|
||||
public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job =
|
||||
messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(this)
|
@ -0,0 +1,73 @@
|
||||
package space.kscience.controls.api
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.provider.Provider
|
||||
|
||||
/**
|
||||
* A hub that could locate multiple devices and redirect actions to them
|
||||
*/
|
||||
public interface DeviceHub : Provider {
|
||||
public val devices: Map<NameToken, 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) {
|
||||
buildMap {
|
||||
fun putAll(prefix: Name, hub: DeviceHub) {
|
||||
hub.devices.forEach {
|
||||
put(prefix + it.key, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
devices.forEach {
|
||||
val name = it.key.asName()
|
||||
put(name, it.value)
|
||||
(it.value as? DeviceHub)?.let { hub ->
|
||||
putAll(name, hub)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
public companion object
|
||||
}
|
||||
|
||||
public operator fun DeviceHub.get(nameToken: NameToken): Device =
|
||||
devices[nameToken] ?: error("Device with name $nameToken not found in $this")
|
||||
|
||||
public fun DeviceHub.getOrNull(name: Name): Device? = when {
|
||||
name.isEmpty() -> this as? Device
|
||||
name.length == 1 -> get(name.firstOrNull()!!)
|
||||
else -> (get(name.firstOrNull()!!) as? DeviceHub)?.getOrNull(name.cutFirst())
|
||||
}
|
||||
|
||||
public operator fun DeviceHub.get(name: Name): Device =
|
||||
getOrNull(name) ?: error("Device with name $name not found in $this")
|
||||
|
||||
public fun DeviceHub.getOrNull(nameString: String): Device? = getOrNull(Name.parse(nameString))
|
||||
|
||||
public operator fun DeviceHub.get(nameString: String): Device =
|
||||
getOrNull(nameString) ?: error("Device with name $nameString not found in $this")
|
||||
|
||||
public suspend fun DeviceHub.readProperty(deviceName: Name, propertyName: String): Meta =
|
||||
this[deviceName].readProperty(propertyName)
|
||||
|
||||
public suspend fun DeviceHub.writeProperty(deviceName: Name, propertyName: String, value: Meta) {
|
||||
this[deviceName].writeProperty(propertyName, value)
|
||||
}
|
||||
|
||||
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: Meta?): Meta? =
|
||||
this[deviceName].execute(command, argument)
|
||||
|
||||
|
||||
//suspend fun DeviceHub.respond(request: Envelope): EnvelopeBuilder {
|
||||
// val target = request.meta[DeviceMessage.TARGET_KEY].string ?: defaultTarget
|
||||
// val device = this[target.toName()]
|
||||
//
|
||||
// return device.respond(device, target, request)
|
||||
//}
|
@ -0,0 +1,234 @@
|
||||
@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, 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,
|
||||
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 [binaryID] is available. The binary itself could not be provided via [DeviceMessage] API.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("binary.notification")
|
||||
public data class BinaryNotificationMessage(
|
||||
val binaryID: 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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? = 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 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,
|
||||
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)
|
@ -0,0 +1,33 @@
|
||||
package space.kscience.controls.api
|
||||
|
||||
import io.ktor.utils.io.core.Closeable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A generic bidirectional sender/receiver object
|
||||
*/
|
||||
public interface Socket<T> : Closeable {
|
||||
/**
|
||||
* Send an object to the socket
|
||||
*/
|
||||
public suspend fun send(data: T)
|
||||
|
||||
/**
|
||||
* Flow of objects received from socket
|
||||
*/
|
||||
public fun receiving(): Flow<T>
|
||||
public fun isOpen(): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect an input to this socket using designated [scope] for it and return a handler [Job].
|
||||
* Multiple inputs could be connected to the same [Socket].
|
||||
*/
|
||||
public fun <T> Socket<T>.connectInput(scope: CoroutineScope, flow: Flow<T>): Job = scope.launch {
|
||||
flow.collect { send(it) }
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
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 info: String? = null,
|
||||
public var metaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||
public var readable: Boolean = true,
|
||||
public var writable: Boolean = false
|
||||
)
|
||||
|
||||
public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.()->Unit){
|
||||
metaDescriptor = MetaDescriptor(block)
|
||||
}
|
||||
|
||||
/**
|
||||
* A descriptor for property
|
||||
*/
|
||||
@Serializable
|
||||
public class ActionDescriptor(public val name: String) {
|
||||
public var info: String? = null
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
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.getOrNull
|
||||
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.NameToken
|
||||
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 top = HashMap<NameToken, Device>()
|
||||
override val devices: Map<NameToken, Device> get() = top
|
||||
|
||||
public fun registerDevice(name: NameToken, device: Device) {
|
||||
top[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(NameToken(name), device)
|
||||
device.launch {
|
||||
device.open()
|
||||
}
|
||||
return 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 = getOrNull(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,112 @@
|
||||
package space.kscience.controls.manager
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
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 -> {
|
||||
if (request.value == null) {
|
||||
invalidate(request.property)
|
||||
} else {
|
||||
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,
|
||||
-> null
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process incoming [DeviceMessage], using hub naming to evaluate target.
|
||||
*/
|
||||
public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): DeviceMessage? {
|
||||
return try {
|
||||
val targetName = request.targetDevice ?: return null
|
||||
val device = getOrNull(targetName) ?: error("The device with name $targetName not found in $this")
|
||||
device.respondMessage(targetName, request)
|
||||
} catch (ex: Exception) {
|
||||
DeviceMessage.error(ex, sourceDevice = Name.EMPTY, targetDevice = request.sourceDevice)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all messages from given [DeviceHub], applying proper relative names.
|
||||
*/
|
||||
public fun DeviceHub.hubMessageFlow(scope: CoroutineScope): Flow<DeviceMessage> {
|
||||
|
||||
//TODO could we avoid using downstream scope?
|
||||
val outbox = MutableSharedFlow<DeviceMessage>()
|
||||
if (this is Device) {
|
||||
messageFlow.onEach {
|
||||
outbox.emit(it)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
//TODO maybe better create map of all devices to limit copying
|
||||
devices.forEach { (token, childDevice) ->
|
||||
val flow = if (childDevice is DeviceHub) {
|
||||
childDevice.hubMessageFlow(scope)
|
||||
} else {
|
||||
childDevice.messageFlow
|
||||
}
|
||||
flow.onEach { deviceMessage ->
|
||||
outbox.emit(
|
||||
deviceMessage.changeSource { token + it }
|
||||
)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
return outbox
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package space.kscience.controls.misc
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.long
|
||||
|
||||
// TODO move to core
|
||||
|
||||
public fun Instant.toMeta(): Meta = Meta {
|
||||
"seconds" put epochSeconds
|
||||
"nanos" put nanosecondsOfSecond
|
||||
}
|
||||
|
||||
public fun Meta.instant(): Instant = value?.long?.let { Instant.fromEpochMilliseconds(it) } ?: Instant.fromEpochSeconds(
|
||||
get("seconds")?.long ?: 0L,
|
||||
get("nanos")?.long ?: 0L,
|
||||
)
|
@ -1,24 +1,38 @@
|
||||
package hep.dataforge.control.ports
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.context.Factory
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.io.Closeable
|
||||
import space.kscience.controls.api.Socket
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
public interface Port: Closeable, ContextAware {
|
||||
public suspend fun send(data: ByteArray)
|
||||
public suspend fun receiving(): Flow<ByteArray>
|
||||
public fun isOpen(): Boolean
|
||||
/**
|
||||
* Raw [ByteArray] port
|
||||
*/
|
||||
public interface Port : ContextAware, Socket<ByteArray>
|
||||
|
||||
/**
|
||||
* A specialized factory for [Port]
|
||||
*/
|
||||
@Type(PortFactory.TYPE)
|
||||
public interface PortFactory : Factory<Port> {
|
||||
public val type: String
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "controls.port"
|
||||
}
|
||||
}
|
||||
|
||||
public typealias PortFactory = Factory<Port>
|
||||
|
||||
public abstract class AbstractPort(override val context: Context, coroutineContext: CoroutineContext = context.coroutineContext) : Port {
|
||||
/**
|
||||
* Common abstraction for [Port] based on [Channel]
|
||||
*/
|
||||
public abstract class AbstractPort(
|
||||
override val context: Context,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
) : Port {
|
||||
|
||||
protected val scope: CoroutineScope = CoroutineScope(coroutineContext + SupervisorJob(coroutineContext[Job]))
|
||||
|
||||
@ -39,18 +53,16 @@ public abstract class AbstractPort(override val context: Context, coroutineConte
|
||||
/**
|
||||
* Internal method to receive data synchronously
|
||||
*/
|
||||
protected fun receive(data: ByteArray) {
|
||||
scope.launch {
|
||||
logger.debug { "[${this@AbstractPort}] RECEIVED: ${data.decodeToString()}" }
|
||||
incoming.send(data)
|
||||
}
|
||||
protected suspend fun receive(data: ByteArray) {
|
||||
logger.debug { "${this@AbstractPort} RECEIVED: ${data.decodeToString()}" }
|
||||
incoming.send(data)
|
||||
}
|
||||
|
||||
private val sendJob = scope.launch {
|
||||
for (data in outgoing) {
|
||||
try {
|
||||
write(data)
|
||||
logger.debug { "[${this@AbstractPort}] SENT: ${data.decodeToString()}" }
|
||||
logger.debug { "${this@AbstractPort} SENT: ${data.decodeToString()}" }
|
||||
} catch (ex: Exception) {
|
||||
if (ex is CancellationException) throw ex
|
||||
logger.error(ex) { "Error while writing data to the port" }
|
||||
@ -67,12 +79,10 @@ public abstract class AbstractPort(override val context: Context, coroutineConte
|
||||
|
||||
/**
|
||||
* Raw flow of incoming data chunks. The chunks are not guaranteed to be complete phrases.
|
||||
* In order to form phrases some condition should used on top of it.
|
||||
* For example [delimitedIncoming] generates phrases with fixed delimiter.
|
||||
* In order to form phrases, some condition should be used on top of it.
|
||||
* For example [stringsDelimitedIncoming] generates phrases with fixed delimiter.
|
||||
*/
|
||||
override suspend fun receiving(): Flow<ByteArray> {
|
||||
return incoming.receiveAsFlow()
|
||||
}
|
||||
override fun receiving(): Flow<ByteArray> = incoming.receiveAsFlow()
|
||||
|
||||
override fun close() {
|
||||
outgoing.close()
|
@ -1,15 +1,12 @@
|
||||
package hep.dataforge.control.ports
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.context.Global
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import space.kscience.dataforge.context.*
|
||||
|
||||
/**
|
||||
* A port that could be closed multiple times and opens automatically on request
|
||||
@ -31,40 +28,37 @@ public class PortProxy(override val context: Context = Global, public val factor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the port is open. If it is already open, does nothing. Otherwise, open a new port.
|
||||
*/
|
||||
public suspend fun open() {
|
||||
port()//ignore result
|
||||
}
|
||||
|
||||
override suspend fun send(data: ByteArray) {
|
||||
port().send(data)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override suspend fun receiving(): Flow<ByteArray> = channelFlow {
|
||||
while (isActive) {
|
||||
override fun receiving(): Flow<ByteArray> = flow {
|
||||
while (true) {
|
||||
try {
|
||||
//recreate port and Flow on cancel
|
||||
//recreate port and Flow on connection problems
|
||||
port().receiving().collect {
|
||||
send(it)
|
||||
emit(it)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
logger.warn(t){"Port read failed. Reconnecting."}
|
||||
//cancel
|
||||
// if (t is CancellationException) {
|
||||
// cancel(t)
|
||||
// }
|
||||
logger.warn{"Port read failed: ${t.message}. Reconnecting."}
|
||||
mutex.withLock {
|
||||
actualPort?.close()
|
||||
actualPort = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}// port().receiving()
|
||||
}
|
||||
|
||||
// open by default
|
||||
override fun isOpen(): Boolean = true
|
||||
|
||||
override fun close() {
|
||||
actualPort?.close()
|
||||
actualPort = null
|
||||
context.launch {
|
||||
mutex.withLock {
|
||||
actualPort?.close()
|
||||
actualPort = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
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 portFactories by lazy {
|
||||
context.gather<PortFactory>(PortFactory.TYPE)
|
||||
}
|
||||
|
||||
private val portCache = mutableMapOf<Meta, Port>()
|
||||
|
||||
/**
|
||||
* Create a new [Port] according to specification
|
||||
*/
|
||||
public fun buildPort(meta: Meta): Port = portCache.getOrPut(meta) {
|
||||
val type by meta.string { error("Port type is not defined") }
|
||||
val factory = portFactories.values.firstOrNull { it.type == type }
|
||||
?: error("Port factory for type $type not found")
|
||||
factory.build(context, meta)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<Ports> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override fun build(context: Context, meta: Meta): Ports = Ports()
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
/**
|
||||
* A port handler for synchronous (request-response) communication with a port. Only one request could be active at a time (others are suspended.
|
||||
* The handler does not guarantee exclusive access to the port so the user mush ensure that no other controller handles port at the moment.
|
||||
*/
|
||||
public class SynchronousPort(public val port: Port, private val mutex: Mutex) : Port by port {
|
||||
/**
|
||||
* Send a single message and wait for the flow of respond messages.
|
||||
*/
|
||||
public suspend fun <R> respond(data: ByteArray, transform: suspend Flow<ByteArray>.() -> R): R = mutex.withLock {
|
||||
port.send(data)
|
||||
transform(port.receiving())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a synchronous wrapper for a port
|
||||
*/
|
||||
public fun Port.synchronous(mutex: Mutex = Mutex()): SynchronousPort = SynchronousPort(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,21 +1,22 @@
|
||||
package hep.dataforge.control.ports
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import io.ktor.utils.io.core.BytePacketBuilder
|
||||
import io.ktor.utils.io.core.readBytes
|
||||
import io.ktor.utils.io.core.reset
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.io.ByteArrayOutput
|
||||
import kotlinx.coroutines.flow.transform
|
||||
|
||||
/**
|
||||
* Transform byte fragments into complete phrases using given delimiter
|
||||
* Transform byte fragments into complete phrases using given delimiter. Not thread safe.
|
||||
*/
|
||||
public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray, expectedMessageSize: Int = 32): Flow<ByteArray> = flow {
|
||||
public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray> {
|
||||
require(delimiter.isNotEmpty()) { "Delimiter must not be empty" }
|
||||
|
||||
var output = ByteArrayOutput(expectedMessageSize)
|
||||
val output = BytePacketBuilder()
|
||||
var matcherPosition = 0
|
||||
|
||||
collect { chunk ->
|
||||
return transform { chunk ->
|
||||
chunk.forEach { byte ->
|
||||
output.writeByte(byte)
|
||||
//matching current symbol in delimiter
|
||||
@ -23,8 +24,9 @@ public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray, expectedMessageSi
|
||||
matcherPosition++
|
||||
if (matcherPosition == delimiter.size) {
|
||||
//full match achieved, sending result
|
||||
emit(output.toByteArray())
|
||||
output = ByteArrayOutput(expectedMessageSize)
|
||||
val bytes = output.build()
|
||||
emit(bytes.readBytes())
|
||||
output.reset()
|
||||
matcherPosition = 0
|
||||
}
|
||||
} else if (matcherPosition > 0) {
|
||||
@ -38,12 +40,16 @@ public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray, expectedMessageSi
|
||||
/**
|
||||
* Transform byte fragments into utf-8 phrases using utf-8 delimiter
|
||||
*/
|
||||
public fun Flow<ByteArray>.withDelimiter(delimiter: String, expectedMessageSize: Int = 32): Flow<String> {
|
||||
public fun Flow<ByteArray>.withStringDelimiter(delimiter: String): Flow<String> {
|
||||
return withDelimiter(delimiter.encodeToByteArray()).map { it.decodeToString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow of delimited phrases
|
||||
*/
|
||||
public suspend fun Port.delimitedIncoming(delimiter: ByteArray, expectedMessageSize: Int = 32): Flow<ByteArray> =
|
||||
receiving().withDelimiter(delimiter, expectedMessageSize)
|
||||
public fun Port.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> = receiving().withDelimiter(delimiter)
|
||||
|
||||
/**
|
||||
* A flow of delimited phrases with string content
|
||||
*/
|
||||
public fun Port.stringsDelimitedIncoming(delimiter: String): Flow<String> = receiving().withStringDelimiter(delimiter)
|
@ -0,0 +1,179 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import space.kscience.controls.api.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.error
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
private suspend fun <D : Device, T> WritableDevicePropertySpec<D, T>.writeMeta(device: D, item: Meta) {
|
||||
write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter"))
|
||||
}
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
private suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? =
|
||||
read(device)?.let(converter::objectToMeta)
|
||||
|
||||
|
||||
private suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
||||
device: D,
|
||||
item: Meta,
|
||||
): Meta? {
|
||||
val arg: I = inputConverter.metaToObject(item) ?: error("Failed to convert $item with $inputConverter")
|
||||
val res = execute(device, arg)
|
||||
return res?.let { outputConverter.objectToMeta(res) }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A base abstractions for [Device], introducing specifications for properties
|
||||
*/
|
||||
public abstract class DeviceBase<D : Device>(
|
||||
final override val context: Context,
|
||||
override val meta: Meta = Meta.EMPTY,
|
||||
) : Device {
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
|
||||
override val coroutineContext: CoroutineContext by lazy {
|
||||
context.newCoroutineContext(
|
||||
SupervisorJob(context.coroutineContext[Job]) +
|
||||
CoroutineName("Device $this") +
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
logger.error(throwable) { "Exception in device $this job" }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logical state store
|
||||
*/
|
||||
private val logicalState: HashMap<String, Meta?> = HashMap()
|
||||
|
||||
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow()
|
||||
|
||||
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 updateLogical(propertyName: String, value: Meta?) {
|
||||
if (value != logicalState[propertyName]) {
|
||||
stateLock.withLock {
|
||||
logicalState[propertyName] = value
|
||||
}
|
||||
if (value != null) {
|
||||
sharedMessageFlow.emit(PropertyChangedMessage(propertyName, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update logical state using given [spec] and its convertor
|
||||
*/
|
||||
public suspend fun <T> updateLogical(spec: DevicePropertySpec<D, T>, value: T) {
|
||||
updateLogical(spec.name, spec.converter.objectToMeta(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")
|
||||
updateLogical(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
|
||||
updateLogical(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 {
|
||||
when (val property = properties[propertyName]) {
|
||||
null -> {
|
||||
//If there is a physical property with a given name, invalidate logical property and write physical one
|
||||
updateLogical(propertyName, value)
|
||||
}
|
||||
|
||||
is WritableDevicePropertySpec -> {
|
||||
invalidate(propertyName)
|
||||
property.writeMeta(self, value)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.INIT
|
||||
protected set
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
override suspend fun open() {
|
||||
super.open()
|
||||
lifecycleState = DeviceLifecycleState.OPEN
|
||||
}
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
override fun close() {
|
||||
lifecycleState = DeviceLifecycleState.CLOSED
|
||||
super.close()
|
||||
}
|
||||
|
||||
abstract override fun toString(): String
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
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 open(): Unit = with(spec) {
|
||||
super.open()
|
||||
self.onOpen()
|
||||
}
|
||||
|
||||
override fun close(): Unit = with(spec) {
|
||||
self.onClose()
|
||||
super.close()
|
||||
}
|
||||
|
||||
override fun toString(): String = "Device(spec=$spec)"
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
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.transformations.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
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
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.ActionDescriptor
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.PropertyChangedMessage
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.dataforge.meta.transformations.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 : Device, 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 WritableDevicePropertySpec<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 : Device, 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.metaToObject(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::metaToObject)
|
||||
|
||||
|
||||
public operator fun <T, D : Device> D.get(propertySpec: DevicePropertySpec<D, T>): T? =
|
||||
getProperty(propertySpec.name)?.let(propertySpec.converter::metaToObject)
|
||||
|
||||
/**
|
||||
* Write typed property state and invalidate logical state
|
||||
*/
|
||||
public suspend fun <T, D : Device> D.write(propertySpec: WritableDevicePropertySpec<D, T>, value: T) {
|
||||
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire and forget variant of property writing. Actual write is performed asynchronously on a [Device] scope
|
||||
*/
|
||||
public operator fun <T, D : Device> D.set(propertySpec: WritableDevicePropertySpec<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.metaToObject(it.value) }
|
||||
|
||||
/**
|
||||
* A type safe property change listener. Uses the device [CoroutineScope].
|
||||
*/
|
||||
public fun <D : Device, T> D.onPropertyChange(
|
||||
spec: DevicePropertySpec<D, T>,
|
||||
callback: suspend PropertyChangedMessage.(T) -> Unit,
|
||||
): Job = messageFlow
|
||||
.filterIsInstance<PropertyChangedMessage>()
|
||||
.filter { it.property == spec.name }
|
||||
.onEach { change ->
|
||||
val newValue = spec.converter.metaToObject(change.value)
|
||||
if (newValue != null) {
|
||||
change.callback(newValue)
|
||||
}
|
||||
}.launchIn(this)
|
||||
|
||||
/**
|
||||
* Call [callback] on initial property value and each value change
|
||||
*/
|
||||
public fun <D : Device, T> D.useProperty(
|
||||
spec: DevicePropertySpec<D, T>,
|
||||
callback: suspend (T) -> Unit,
|
||||
): Job = launch {
|
||||
callback(read(spec))
|
||||
messageFlow
|
||||
.filterIsInstance<PropertyChangedMessage>()
|
||||
.filter { it.property == spec.name }
|
||||
.collect { change ->
|
||||
val newValue = spec.converter.metaToObject(change.value)
|
||||
if (newValue != null) {
|
||||
callback(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the logical state of a property
|
||||
*/
|
||||
public suspend fun <D : Device> 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)
|
@ -0,0 +1,240 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.withContext
|
||||
import space.kscience.controls.api.ActionDescriptor
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
public object UnitMetaConverter: MetaConverter<Unit>{
|
||||
override fun metaToObject(meta: Meta): Unit = Unit
|
||||
|
||||
override fun objectToMeta(obj: Unit): Meta = Meta.EMPTY
|
||||
}
|
||||
|
||||
public val MetaConverter.Companion.unit: MetaConverter<Unit> get() = UnitMetaConverter
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class DeviceSpec<D : Device> {
|
||||
//initializing meta 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 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>,
|
||||
readOnlyProperty: KProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, DevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||
//TODO add type from converter
|
||||
writable = true
|
||||
}.apply(descriptorBuilder)
|
||||
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
||||
readOnlyProperty.get(device)
|
||||
}
|
||||
}
|
||||
registerProperty(deviceProperty)
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceProperty
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> mutableProperty(
|
||||
converter: MetaConverter<T>,
|
||||
readWriteProperty: KMutableProperty1<D, T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||
//TODO add the type from converter
|
||||
writable = true
|
||||
}.apply(descriptorBuilder)
|
||||
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
||||
readWriteProperty.get(device)
|
||||
}
|
||||
|
||||
override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) {
|
||||
readWriteProperty.set(device, value)
|
||||
}
|
||||
}
|
||||
registerProperty(deviceProperty)
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceProperty
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> 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(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T? = withContext(device.coroutineContext) { device.read() }
|
||||
}
|
||||
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.() -> T?,
|
||||
write: suspend D.(T) -> Unit,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||
val propertyName = name ?: property.name
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply(descriptorBuilder)
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T? = withContext(device.coroutineContext) { device.read() }
|
||||
|
||||
override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) {
|
||||
device.write(value)
|
||||
}
|
||||
}
|
||||
_properties[propertyName] = deviceProperty
|
||||
ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<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 ->
|
||||
val actionName = name ?: property.name
|
||||
val deviceAction = object : DeviceActionSpec<D, I, O> {
|
||||
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply(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 [Meta] and returns [Meta]. No conversions are done
|
||||
*/
|
||||
public fun 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* An action that takes no parameters and returns no values
|
||||
*/
|
||||
public fun 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a mutable logical property for a device
|
||||
*/
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.logicalProperty(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<Any?, WritableDevicePropertySpec<D, T>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||
val propertyName = name ?: property.name
|
||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
|
||||
//TODO add type from converter
|
||||
writable = true
|
||||
}.apply(descriptorBuilder)
|
||||
|
||||
override val converter: MetaConverter<T> = converter
|
||||
|
||||
override suspend fun read(device: D): T? = device.getProperty(propertyName)?.let(converter::metaToObject)
|
||||
|
||||
override suspend fun write(device: D, value: T): Unit =
|
||||
device.writeProperty(propertyName, converter.objectToMeta(value))
|
||||
}
|
||||
registerProperty(deviceProperty)
|
||||
ReadOnlyProperty { _, _ ->
|
||||
deviceProperty
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.api.Device
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* 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 called context. In order 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, reader: suspend D.() -> R): Flow<R> = flow {
|
||||
while (isActive) {
|
||||
delay(interval)
|
||||
launch {
|
||||
emit(reader())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a recurring (with a fixed delay) task on a device.
|
||||
*/
|
||||
public fun <D : Device> D.doRecurring(interval: Duration, task: suspend D.() -> Unit): Job = launch {
|
||||
while (isActive) {
|
||||
delay(interval)
|
||||
launch {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
public fun Double.asMeta(): Meta = Meta(asValue())
|
||||
|
||||
//TODO to be moved to DF
|
||||
public object DurationConverter : MetaConverter<Duration> {
|
||||
override fun metaToObject(meta: Meta): Duration = meta.value?.double?.toDuration(DurationUnit.SECONDS)
|
||||
?: run {
|
||||
val unit: DurationUnit = meta["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
|
||||
val value = meta[Meta.VALUE_KEY].double ?: error("No value present for Duration")
|
||||
return@run value.toDuration(unit)
|
||||
}
|
||||
|
||||
override fun objectToMeta(obj: Duration): Meta = obj.toDouble(DurationUnit.SECONDS).asMeta()
|
||||
}
|
||||
|
||||
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter
|
@ -0,0 +1,145 @@
|
||||
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.ValueType
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
//read only delegates
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Boolean?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
|
||||
MetaConverter.boolean,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.BOOLEAN)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
name,
|
||||
read
|
||||
)
|
||||
|
||||
private inline fun numberDescriptor(
|
||||
crossinline descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): PropertyDescriptor.() -> Unit = {
|
||||
metaDescriptor {
|
||||
type(ValueType.NUMBER)
|
||||
}
|
||||
descriptorBuilder()
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.numberProperty(
|
||||
name: String? = null,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
read: suspend D.() -> 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.() -> 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.() -> String?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
|
||||
MetaConverter.string,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.STRING)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
name,
|
||||
read
|
||||
)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Meta?
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
|
||||
MetaConverter.meta,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.STRING)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
name,
|
||||
read
|
||||
)
|
||||
|
||||
//read-write delegates
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Boolean?,
|
||||
write: suspend D.(Boolean) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Boolean>>> =
|
||||
mutableProperty(
|
||||
MetaConverter.boolean,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.BOOLEAN)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
name,
|
||||
read,
|
||||
write
|
||||
)
|
||||
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.numberProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Number,
|
||||
write: suspend D.(Number) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
||||
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Double,
|
||||
write: suspend D.(Double) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
||||
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> String,
|
||||
write: suspend D.(String) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
||||
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.() -> Meta,
|
||||
write: suspend D.(Meta) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Meta>>> =
|
||||
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
|
@ -0,0 +1,18 @@
|
||||
package space.kscience.controls.api
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import space.kscience.controls.spec.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)
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.error
|
||||
import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.ByteChannel
|
||||
import java.nio.channels.DatagramChannel
|
||||
import java.nio.channels.SocketChannel
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
public fun ByteBuffer.toArray(limit: Int = limit()): ByteArray {
|
||||
rewind()
|
||||
val response = ByteArray(limit)
|
||||
get(response)
|
||||
rewind()
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* A port based on nio [ByteChannel]
|
||||
*/
|
||||
public class ChannelPort(
|
||||
context: Context,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
channelBuilder: suspend () -> ByteChannel,
|
||||
) : AbstractPort(context, coroutineContext), AutoCloseable {
|
||||
|
||||
private val futureChannel: Deferred<ByteChannel> = this.scope.async(Dispatchers.IO) {
|
||||
channelBuilder()
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler to await port connection
|
||||
*/
|
||||
public val startJob: Job get() = futureChannel
|
||||
|
||||
private val listenerJob = this.scope.launch(Dispatchers.IO) {
|
||||
val channel = futureChannel.await()
|
||||
val buffer = ByteBuffer.allocate(1024)
|
||||
while (isActive) {
|
||||
try {
|
||||
val num = channel.read(buffer)
|
||||
if (num > 0) {
|
||||
receive(buffer.toArray(num))
|
||||
}
|
||||
if (num < 0) cancel("The input channel is exhausted")
|
||||
} catch (ex: Exception) {
|
||||
logger.error(ex) { "Channel read error" }
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
|
||||
futureChannel.await().write(ByteBuffer.wrap(data))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun close() {
|
||||
listenerJob.cancel()
|
||||
if (futureChannel.isCompleted) {
|
||||
futureChannel.getCompleted().close()
|
||||
} else {
|
||||
futureChannel.cancel()
|
||||
}
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PortFactory] for TCP connections
|
||||
*/
|
||||
public object TcpPort : PortFactory {
|
||||
|
||||
override val type: String = "tcp"
|
||||
|
||||
public fun open(
|
||||
context: Context,
|
||||
host: String,
|
||||
port: Int,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
): ChannelPort = ChannelPort(context, coroutineContext) {
|
||||
SocketChannel.open(InetSocketAddress(host, port))
|
||||
}
|
||||
|
||||
override fun build(context: Context, meta: Meta): ChannelPort {
|
||||
val host = meta["host"].string ?: "localhost"
|
||||
val port = meta["port"].int ?: error("Port value for TCP port is not defined in $meta")
|
||||
return open(context, host, port)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A [PortFactory] for UDP connections
|
||||
*/
|
||||
public object UdpPort : PortFactory {
|
||||
|
||||
override val type: String = "udp"
|
||||
|
||||
/**
|
||||
* Connect a datagram channel to a remote host/port. If [localPort] is provided, it is used to bind local port for receiving messages.
|
||||
*/
|
||||
public fun open(
|
||||
context: Context,
|
||||
remoteHost: String,
|
||||
remotePort: Int,
|
||||
localPort: Int? = null,
|
||||
localHost: String = "localhost",
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
): ChannelPort = ChannelPort(context, coroutineContext) {
|
||||
DatagramChannel.open().apply {
|
||||
//bind the channel to a local port to receive messages
|
||||
localPort?.let { bind(InetSocketAddress(localHost, localPort)) }
|
||||
//connect to remote port to send messages
|
||||
connect(InetSocketAddress(remoteHost, remotePort))
|
||||
context.logger.info { "Connected to UDP $remotePort on $remoteHost" }
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(context: Context, meta: Meta): ChannelPort {
|
||||
val remoteHost by meta.string { error("Remote host is not specified") }
|
||||
val remotePort by meta.number { error("Remote port is not specified") }
|
||||
val localHost: String? by meta.string()
|
||||
val localPort: Int? by meta.int()
|
||||
return open(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost")
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
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.parseAsName
|
||||
|
||||
/**
|
||||
* 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){
|
||||
PortFactory.TYPE -> mapOf(
|
||||
TcpPort.type.parseAsName() to TcpPort,
|
||||
UdpPort.type.parseAsName() 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,4 +1,4 @@
|
||||
package hep.dataforge.control.ports
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
29
controls-magix/README.md
Normal file
29
controls-magix/README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Module controls-magix
|
||||
|
||||
Magix service for binding controls devices (both as RPC client and server)
|
||||
|
||||
## Features
|
||||
|
||||
- [controlsMagix](src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt) : Connect a `DeviceManage` with one or many devices to the Magix endpoint
|
||||
- [DeviceClient](src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt) : A remote connector to Controls-kt device via Magix
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-magix:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-magix:0.2.0")
|
||||
}
|
||||
```
|
199
controls-magix/api/controls-magix.api
Normal file
199
controls-magix/api/controls-magix.api
Normal file
@ -0,0 +1,199 @@
|
||||
public final class space/kscience/controls/client/ControlsMagixKt {
|
||||
public static final fun getMagixFormat (Lspace/kscience/controls/manager/DeviceManager$Companion;)Lspace/kscience/magix/api/MagixFormat;
|
||||
public static final fun launchMagixService (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;)Lkotlinx/coroutines/Job;
|
||||
public static synthetic fun launchMagixService$default (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/client/DeviceClient : space/kscience/controls/api/Device {
|
||||
public fun <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;
|
||||
}
|
||||
|
39
controls-magix/build.gradle.kts
Normal file
39
controls-magix/build.gradle.kts
Normal file
@ -0,0 +1,39 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
Magix service for binding controls devices (both as RPC client and server)
|
||||
""".trimIndent()
|
||||
|
||||
kscience {
|
||||
jvm()
|
||||
js()
|
||||
useSerialization {
|
||||
json()
|
||||
}
|
||||
dependencies {
|
||||
api(projects.magix.magixApi)
|
||||
api(projects.controlsCore)
|
||||
api("com.benasher44:uuid:0.8.0")
|
||||
}
|
||||
}
|
||||
|
||||
readme {
|
||||
maturity = Maturity.EXPERIMENTAL
|
||||
|
||||
feature("controlsMagix", ref = "src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt"){
|
||||
"""
|
||||
Connect a `DeviceManage` with one or many devices to the Magix endpoint
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
feature("DeviceClient", ref = "src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt"){
|
||||
"""
|
||||
A remote connector to Controls-kt device via Magix
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package space.kscience.controls.client
|
||||
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.newCoroutineContext
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import space.kscience.controls.api.*
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.magix.api.MagixEndpoint
|
||||
import space.kscience.magix.api.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(
|
||||
override val context: Context,
|
||||
private val deviceName: Name,
|
||||
incomingFlow: Flow<DeviceMessage>,
|
||||
private val send: suspend (DeviceMessage) -> Unit,
|
||||
) : Device {
|
||||
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
override val coroutineContext: CoroutineContext = newCoroutineContext(context.coroutineContext)
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
private val propertyCache = HashMap<String, Meta>()
|
||||
|
||||
override var propertyDescriptors: Collection<PropertyDescriptor> = emptyList()
|
||||
private set
|
||||
|
||||
override var actionDescriptors: Collection<ActionDescriptor> = emptyList()
|
||||
private set
|
||||
|
||||
private val flowInternal = incomingFlow.filter {
|
||||
it.sourceDevice == deviceName
|
||||
}.shareIn(this, started = SharingStarted.Eagerly).also {
|
||||
it.onEach { message ->
|
||||
when (message) {
|
||||
is PropertyChangedMessage -> mutex.withLock {
|
||||
propertyCache[message.property] = message.value
|
||||
}
|
||||
|
||||
is DescriptionMessage -> mutex.withLock {
|
||||
propertyDescriptors = message.properties
|
||||
actionDescriptors = message.actions
|
||||
}
|
||||
|
||||
else -> {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
override val messageFlow: Flow<DeviceMessage> get() = flowInternal
|
||||
|
||||
|
||||
override suspend fun readProperty(propertyName: String): Meta {
|
||||
send(
|
||||
PropertyGetMessage(propertyName, targetDevice = deviceName)
|
||||
)
|
||||
return flowInternal.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 flowInternal.filterIsInstance<ActionResultMessage>().first {
|
||||
it.action == actionName && it.requestId == id
|
||||
}.result
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
override val lifecycleState: DeviceLifecycleState = DeviceLifecycleState.OPEN
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a remote device via this endpoint.
|
||||
*
|
||||
* @param context a [Context] to run device in
|
||||
* @param endpointName the name of endpoint in Magix to connect to
|
||||
* @param deviceName the name of device within endpoint
|
||||
*/
|
||||
public fun MagixEndpoint.remoteDevice(context: Context, endpointName: String, deviceName: Name): DeviceClient {
|
||||
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(endpointName)).map { it.second }
|
||||
return DeviceClient(context, deviceName, subscription) {
|
||||
send(DeviceManager.magixFormat, it, endpointName, id = stringUID())
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package space.kscience.controls.client
|
||||
|
||||
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.*
|
||||
|
||||
|
||||
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 {
|
||||
"controls[${request.payload.hashCode().toString(16)}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1)
|
||||
*/
|
||||
public fun DeviceManager.launchMagixService(
|
||||
endpoint: MagixEndpoint,
|
||||
endpointID: String = controlsMagixFormat.defaultFormat,
|
||||
): Job = context.launch {
|
||||
endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID)).onEach { (request, payload) ->
|
||||
val responsePayload = respondHubMessage(payload)
|
||||
if (responsePayload != null) {
|
||||
endpoint.send(
|
||||
format = controlsMagixFormat,
|
||||
payload = responsePayload,
|
||||
source = endpointID,
|
||||
target = request.sourceEndpoint,
|
||||
id = generateId(request),
|
||||
parentId = request.id
|
||||
)
|
||||
}
|
||||
}.catch { error ->
|
||||
logger.error(error) { "Error while responding to message: ${error.message}" }
|
||||
}.launchIn(this)
|
||||
|
||||
hubMessageFlow(this).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)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,119 @@
|
||||
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?
|
||||
)
|
@ -0,0 +1,152 @@
|
||||
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.get
|
||||
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.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 = get(payload.device)
|
||||
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)
|
||||
}
|
||||
}
|
31
controls-modbus/README.md
Normal file
31
controls-modbus/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# 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.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-modbus:0.2.0")
|
||||
}
|
||||
```
|
166
controls-modbus/api/controls-modbus.api
Normal file
166
controls-modbus/api/controls-modbus.api
Normal file
@ -0,0 +1,166 @@
|
||||
public final class space/kscience/controls/modbus/DeviceProcessImageBuilder {
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/DigitalIn;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Lspace/kscience/controls/spec/DevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/DigitalIn;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)V
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRange;Lspace/kscience/controls/spec/DevicePropertySpec;)V
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/SimpleInputRegister;
|
||||
public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Lspace/kscience/controls/spec/DevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/SimpleInputRegister;
|
||||
public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut;
|
||||
public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/DigitalIn;
|
||||
public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister;
|
||||
public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/SimpleInputRegister;
|
||||
public final fun bindAction (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lkotlin/jvm/functions/Function3;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut;
|
||||
public final fun bindAction (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Lkotlin/jvm/functions/Function3;)Ljava/util/List;
|
||||
public final fun bindAction (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lkotlin/jvm/functions/Function3;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister;
|
||||
public final fun getImage ()Lcom/ghgande/j2mod/modbus/procimg/ProcessImageImplementation;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/modbus/DeviceProcessImageKt {
|
||||
public static final fun bindProcessImage (Lspace/kscience/controls/api/Device;ZLkotlin/jvm/functions/Function1;)Lcom/ghgande/j2mod/modbus/procimg/ProcessImage;
|
||||
public static synthetic fun bindProcessImage$default (Lspace/kscience/controls/api/Device;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/ProcessImage;
|
||||
}
|
||||
|
||||
public abstract interface class space/kscience/controls/modbus/ModbusDevice : space/kscience/controls/api/Device {
|
||||
public abstract fun getClientId ()I
|
||||
public abstract fun getMaster ()Lcom/ghgande/j2mod/modbus/facade/AbstractModbusMaster;
|
||||
public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Z
|
||||
public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Z
|
||||
public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
|
||||
public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Ljava/lang/Object;Lkotlin/reflect/KProperty;)S
|
||||
public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRange;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
|
||||
public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Ljava/lang/Object;Lkotlin/reflect/KProperty;)S
|
||||
public fun setValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Ljava/lang/Object;Lkotlin/reflect/KProperty;Z)V
|
||||
public fun setValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
|
||||
public fun setValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Ljava/lang/Object;Lkotlin/reflect/KProperty;S)V
|
||||
}
|
||||
|
||||
public class space/kscience/controls/modbus/ModbusDeviceBySpec : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/modbus/ModbusDevice {
|
||||
public fun <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
|
||||
}
|
||||
|
39
controls-modbus/build.gradle.kts
Normal file
39
controls-modbus/build.gradle.kts
Normal file
@ -0,0 +1,39 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols
|
||||
""".trimIndent()
|
||||
|
||||
|
||||
dependencies {
|
||||
api(projects.controlsCore)
|
||||
api("com.ghgande:j2mod:3.1.1")
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = Maturity.EXPERIMENTAL
|
||||
|
||||
feature("modbusRegistryMap", ref = "src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt"){
|
||||
"""
|
||||
Type-safe modbus registry map. Allows to define both single-register and multi-register entries (using DataForge IO).
|
||||
Automatically checks consistency.
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
feature("modbusProcessImage", ref = "src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt"){
|
||||
"""
|
||||
Binding of slave (server) modbus device to Controls-kt device
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
feature("modbusDevice", ref = "src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt"){
|
||||
"""
|
||||
A device with additional methods to work with modbus registers.
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
package space.kscience.controls.modbus
|
||||
|
||||
import com.ghgande.j2mod.modbus.procimg.*
|
||||
import io.ktor.utils.io.core.buildPacket
|
||||
import io.ktor.utils.io.core.readByteBuffer
|
||||
import io.ktor.utils.io.core.writeShort
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.spec.DevicePropertySpec
|
||||
import space.kscience.controls.spec.WritableDevicePropertySpec
|
||||
import space.kscience.controls.spec.set
|
||||
import space.kscience.controls.spec.useProperty
|
||||
|
||||
|
||||
public class DeviceProcessImageBuilder<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: WritableDevicePropertySpec<D, Boolean>,
|
||||
): ObservableDigitalOut = bind(key) { coil ->
|
||||
coil.addObserver { _, _ ->
|
||||
device[propertySpec] = coil.isSet
|
||||
}
|
||||
device.useProperty(propertySpec) { value ->
|
||||
coil.set(value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.DiscreteInput,
|
||||
block: D.(SimpleDigitalIn) -> Unit = {},
|
||||
): DigitalIn {
|
||||
val input = SimpleDigitalIn()
|
||||
device.block(input)
|
||||
image.addDigitalIn(key.address, input)
|
||||
return input
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.DiscreteInput,
|
||||
propertySpec: DevicePropertySpec<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: WritableDevicePropertySpec<D, Short>,
|
||||
): ObservableRegister = bind(key) { register ->
|
||||
register.addObserver { _, _ ->
|
||||
device[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 packet = buildPacket {
|
||||
key.format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
registers.forEachIndexed { index, register ->
|
||||
register.setValue(packet.getShort(index * 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> bind(key: ModbusRegistryKey.HoldingRange<T>, propertySpec: WritableDevicePropertySpec<D, T>) {
|
||||
val registers = List(key.count) {
|
||||
ObservableRegister()
|
||||
}
|
||||
registers.forEachIndexed { index, register ->
|
||||
register.addObserver { _, _ ->
|
||||
val packet = buildPacket {
|
||||
registers.forEach { value ->
|
||||
writeShort(value.toShort())
|
||||
}
|
||||
}
|
||||
device[propertySpec] = key.format.readObject(packet)
|
||||
}
|
||||
image.addRegister(key.address + index, register)
|
||||
}
|
||||
|
||||
device.useProperty(propertySpec) { value ->
|
||||
val packet = buildPacket {
|
||||
key.format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
registers.forEachIndexed { index, observableRegister ->
|
||||
observableRegister.setValue(packet.getShort(index * 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun bindAction(
|
||||
key: ModbusRegistryKey.Coil,
|
||||
action: suspend D.(Boolean) -> Unit,
|
||||
): ObservableDigitalOut {
|
||||
val coil = ObservableDigitalOut()
|
||||
coil.addObserver { _, _ ->
|
||||
device.launch {
|
||||
device.action(coil.isSet)
|
||||
}
|
||||
}
|
||||
image.addDigitalOut(key.address, coil)
|
||||
return coil
|
||||
}
|
||||
|
||||
public fun bindAction(
|
||||
key: ModbusRegistryKey.HoldingRegister,
|
||||
action: suspend D.(Short) -> Unit,
|
||||
): ObservableRegister {
|
||||
val register = ObservableRegister()
|
||||
register.addObserver { _, _ ->
|
||||
|
||||
with(device) {
|
||||
launch {
|
||||
action(register.toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
image.addRegister(key.address, register)
|
||||
return register
|
||||
}
|
||||
|
||||
public fun <T> bindAction(
|
||||
key: ModbusRegistryKey.HoldingRange<T>,
|
||||
action: suspend D.(T) -> Unit,
|
||||
): List<ObservableRegister> {
|
||||
val registers = List(key.count) {
|
||||
ObservableRegister()
|
||||
}
|
||||
registers.forEachIndexed { index, register ->
|
||||
register.addObserver { _, _ ->
|
||||
val packet = buildPacket {
|
||||
registers.forEach { value ->
|
||||
writeShort(value.toShort())
|
||||
}
|
||||
}
|
||||
device.launch {
|
||||
device.action(key.format.readObject(packet))
|
||||
}
|
||||
}
|
||||
image.addRegister(key.address + index, register)
|
||||
}
|
||||
|
||||
return registers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the device to Modbus slave (server) image.
|
||||
*/
|
||||
public fun <D : Device> D.bindProcessImage(
|
||||
openOnBind: Boolean = true,
|
||||
binding: DeviceProcessImageBuilder<D>.() -> Unit,
|
||||
): ProcessImage {
|
||||
val image = SimpleProcessImage()
|
||||
DeviceProcessImageBuilder(this, image).apply(binding)
|
||||
if (openOnBind) {
|
||||
launch {
|
||||
open()
|
||||
}
|
||||
}
|
||||
return image
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
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 io.ktor.utils.io.core.ByteReadPacket
|
||||
import io.ktor.utils.io.core.buildPacket
|
||||
import io.ktor.utils.io.core.readByteBuffer
|
||||
import io.ktor.utils.io.core.writeShort
|
||||
import space.kscience.controls.api.Device
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
/**
|
||||
* A Modbus device backed by j2mod client
|
||||
*/
|
||||
public interface ModbusDevice : Device {
|
||||
|
||||
/**
|
||||
* Client id for this specific device
|
||||
*/
|
||||
public val clientId: 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.readObject(packet)
|
||||
}
|
||||
|
||||
|
||||
public operator fun ModbusRegistryKey.HoldingRegister.getValue(thisRef: Any?, property: KProperty<*>): Short =
|
||||
readHoldingRegister(address)
|
||||
|
||||
public operator fun ModbusRegistryKey.HoldingRegister.setValue(
|
||||
thisRef: Any?,
|
||||
property: KProperty<*>,
|
||||
value: Short,
|
||||
) {
|
||||
writeHoldingRegister(address, value)
|
||||
}
|
||||
|
||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val packet = readInputRegistersToPacket(address, count)
|
||||
return format.readObject(packet)
|
||||
}
|
||||
|
||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.setValue(
|
||||
thisRef: Any?,
|
||||
property: KProperty<*>,
|
||||
value: T,
|
||||
) {
|
||||
val buffer = buildPacket {
|
||||
format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
writeHoldingRegisters(address, buffer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read multiple sequential modbus coils (bit-values)
|
||||
*/
|
||||
public fun ModbusDevice.readCoils(address: Int, count: Int): BitVector =
|
||||
master.readCoils(clientId, address, count)
|
||||
|
||||
public fun ModbusDevice.readCoil(address: Int): Boolean =
|
||||
master.readCoils(clientId, 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(clientId, address, bitVector)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.writeCoil(address: Int, value: Boolean) {
|
||||
master.writeCoil(clientId, address, value)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.writeCoil(key: ModbusRegistryKey.Coil, value: Boolean) {
|
||||
master.writeCoil(clientId, key.address, value)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.readInputDiscretes(address: Int, count: Int): BitVector =
|
||||
master.readInputDiscretes(clientId, address, count)
|
||||
|
||||
public fun ModbusDevice.readInputDiscrete(address: Int): Boolean =
|
||||
master.readInputDiscretes(clientId, address, 1).getBit(0)
|
||||
|
||||
public fun ModbusDevice.readInputRegisters(address: Int, count: Int): List<InputRegister> =
|
||||
master.readInputRegisters(clientId, 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(): ByteReadPacket = buildPacket {
|
||||
forEach { value ->
|
||||
writeShort(value.toShort())
|
||||
}
|
||||
}
|
||||
|
||||
public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||
master.readInputRegisters(clientId, address, count).toBuffer()
|
||||
|
||||
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||
master.readInputRegisters(clientId, address, count).toPacket()
|
||||
|
||||
public fun ModbusDevice.readDoubleInput(address: Int): Double =
|
||||
readInputRegistersToBuffer(address, Double.SIZE_BYTES).getDouble()
|
||||
|
||||
public fun ModbusDevice.readInputRegister(address: Int): Short =
|
||||
readInputRegisters(address, 1).first().toShort()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List<Register> =
|
||||
master.readMultipleRegisters(clientId, address, count).toList()
|
||||
|
||||
/**
|
||||
* Read a number of registers to a [ByteBuffer]
|
||||
* @param address of a register
|
||||
* @param count number of 2-bytes registers to read. Buffer size is 2*[count]
|
||||
*/
|
||||
public fun ModbusDevice.readHoldingRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||
master.readMultipleRegisters(clientId, address, count).toBuffer()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||
master.readMultipleRegisters(clientId, address, count).toPacket()
|
||||
|
||||
public fun ModbusDevice.readDoubleRegister(address: Int): Double =
|
||||
readHoldingRegistersToBuffer(address, Double.SIZE_BYTES).getDouble()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegister(address: Int): Short =
|
||||
readHoldingRegisters(address, 1).first().toShort()
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegisters(address: Int, values: ShortArray): Int =
|
||||
master.writeMultipleRegisters(
|
||||
clientId,
|
||||
address,
|
||||
Array<Register>(values.size) { SimpleInputRegister(values[it].toInt()) }
|
||||
)
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegister(address: Int, value: Short): Int =
|
||||
master.writeSingleRegister(
|
||||
clientId,
|
||||
address,
|
||||
SimpleInputRegister(value.toInt())
|
||||
)
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegister(key: ModbusRegistryKey.HoldingRegister, value: Short): Int =
|
||||
writeHoldingRegister(key.address, value)
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegisters(address: Int, buffer: ByteBuffer): Int {
|
||||
val array: ShortArray = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) }
|
||||
|
||||
return writeHoldingRegisters(address, array)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.writeShortRegister(address: Int, value: Short) {
|
||||
master.writeSingleRegister(address, SimpleInputRegister(value.toInt()))
|
||||
}
|
||||
|
||||
public fun ModbusDevice.modbusRegister(
|
||||
address: Int,
|
||||
): ReadWriteProperty<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)
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
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.NameToken
|
||||
|
||||
/**
|
||||
* A variant of [DeviceBySpec] that includes Modbus RTU/TCP/UDP client
|
||||
*/
|
||||
public open class ModbusDeviceBySpec<D: Device>(
|
||||
context: Context,
|
||||
spec: DeviceSpec<D>,
|
||||
override val clientId: Int,
|
||||
override val master: AbstractModbusMaster,
|
||||
private val disposeMasterOnClose: Boolean = true,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta){
|
||||
override suspend fun open() {
|
||||
master.connect()
|
||||
super<DeviceBySpec>.open()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if(disposeMasterOnClose){
|
||||
master.disconnect()
|
||||
}
|
||||
super<ModbusDevice>.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ModbusHub(
|
||||
public val context: Context,
|
||||
public val masterBuilder: () -> AbstractModbusMaster,
|
||||
public val specs: Map<NameToken, Pair<Int, DeviceSpec<*>>>,
|
||||
) : DeviceHub, AutoCloseable {
|
||||
|
||||
public val master: AbstractModbusMaster by lazy(masterBuilder)
|
||||
|
||||
override val devices: Map<NameToken, ModbusDevice> by lazy {
|
||||
specs.mapValues { (_, pair) ->
|
||||
ModbusDeviceBySpec(
|
||||
context,
|
||||
pair.second,
|
||||
pair.first,
|
||||
master
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
master.disconnect()
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package space.kscience.controls.modbus
|
||||
|
||||
import space.kscience.dataforge.io.IOFormat
|
||||
|
||||
|
||||
public sealed class ModbusRegistryKey {
|
||||
public abstract val address: Int
|
||||
public open val count: Int = 1
|
||||
|
||||
|
||||
/**
|
||||
* Read-only boolean value
|
||||
*/
|
||||
public 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)"
|
||||
}
|
||||
|
||||
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)"
|
||||
|
||||
|
||||
}
|
||||
|
||||
public open class HoldingRegister(override val address: Int) : ModbusRegistryKey() {
|
||||
override fun toString(): String = "HoldingRegister(address=$address)"
|
||||
}
|
||||
|
||||
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)"
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
protected fun coil(address: Int, description: String = ""): ModbusRegistryKey.Coil =
|
||||
register(ModbusRegistryKey.Coil(address), description)
|
||||
|
||||
|
||||
protected fun discrete(address: Int, description: String = ""): ModbusRegistryKey.DiscreteInput =
|
||||
register(ModbusRegistryKey.DiscreteInput(address), description)
|
||||
|
||||
protected fun input(address: Int, description: String = ""): ModbusRegistryKey.InputRegister =
|
||||
register(ModbusRegistryKey.InputRegister(address), description)
|
||||
|
||||
protected fun <T> input(
|
||||
address: Int,
|
||||
count: Int,
|
||||
reader: IOFormat<T>,
|
||||
description: String = "",
|
||||
): ModbusRegistryKey.InputRange<T> =
|
||||
register(ModbusRegistryKey.InputRange(address, count, reader), description)
|
||||
|
||||
protected fun register(address: Int, description: String = ""): ModbusRegistryKey.HoldingRegister =
|
||||
register(ModbusRegistryKey.HoldingRegister(address), description)
|
||||
|
||||
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 {
|
||||
public fun validate(map: ModbusRegistryMap) {
|
||||
var lastCoil: ModbusRegistryKey.Coil? = null
|
||||
var lastDiscreteInput: ModbusRegistryKey.DiscreteInput? = null
|
||||
var lastInput: ModbusRegistryKey.InputRegister? = null
|
||||
var lastRegister: ModbusRegistryKey.HoldingRegister? = null
|
||||
map.entries.keys.sortedBy { it.address }.forEach { key ->
|
||||
when (key) {
|
||||
is ModbusRegistryKey.Coil -> if (lastCoil?.let { key.address >= it.address + it.count } != false) {
|
||||
lastCoil = key
|
||||
} else {
|
||||
error("Key $lastCoil overlaps with key $key")
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.DiscreteInput -> if (lastDiscreteInput?.let { key.address >= it.address + it.count } != false) {
|
||||
lastDiscreteInput = key
|
||||
} else {
|
||||
error("Key $lastDiscreteInput overlaps with key $key")
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.InputRegister -> if (lastInput?.let { key.address >= it.address + it.count } != false) {
|
||||
lastInput = key
|
||||
} else {
|
||||
error("Key $lastInput overlaps with key $key")
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.HoldingRegister -> if (lastRegister?.let { key.address >= it.address + it.count } != false) {
|
||||
lastRegister = key
|
||||
} else {
|
||||
error("Key $lastRegister overlaps with key $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ModbusRegistryKey.sectionNumber
|
||||
get() = when (this) {
|
||||
is ModbusRegistryKey.Coil -> 1
|
||||
is ModbusRegistryKey.DiscreteInput -> 2
|
||||
is ModbusRegistryKey.HoldingRegister -> 4
|
||||
is ModbusRegistryKey.InputRegister -> 3
|
||||
}
|
||||
|
||||
public fun print(map: ModbusRegistryMap, to: Appendable = System.out) {
|
||||
validate(map)
|
||||
map.entries.entries
|
||||
.sortedWith(
|
||||
Comparator.comparingInt<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}"
|
||||
}
|
||||
to.appendLine("${typeString}\t$rangeString\t$description")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
29
controls-opcua/README.md
Normal file
29
controls-opcua/README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Module controls-opcua
|
||||
|
||||
A client and server connectors for OPC-UA via Eclipse Milo
|
||||
|
||||
## Features
|
||||
|
||||
- [opcuaClient](src/main/kotlin/space/kscience/controls/opcua/client) : Connect a Controls-kt as a client to OPC UA server
|
||||
- [opcuaServer](src/main/kotlin/space/kscience/controls/opcua/server) : Create an OPC UA server on top of Controls-kt device (or device hub)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-opcua:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-opcua:0.2.0")
|
||||
}
|
||||
```
|
82
controls-opcua/api/controls-opcua.api
Normal file
82
controls-opcua/api/controls-opcua.api
Normal file
@ -0,0 +1,82 @@
|
||||
public final class space/kscience/controls/opcua/client/MetaBsdParser : org/eclipse/milo/opcua/binaryschema/parser/BsdParser {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/client/MetaBsdParserKt {
|
||||
public static final fun toMeta (Lorg/eclipse/milo/opcua/stack/core/types/builtin/Variant;Lorg/eclipse/milo/opcua/stack/core/serialization/SerializationContext;)Lspace/kscience/dataforge/meta/Meta;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/client/MiloConfiguration : space/kscience/dataforge/meta/Scheme {
|
||||
public static final field Companion Lspace/kscience/controls/opcua/client/MiloConfiguration$Companion;
|
||||
public fun <init> ()V
|
||||
public final fun getEndpointUrl ()Ljava/lang/String;
|
||||
public final fun getSecurityPolicy ()Lorg/eclipse/milo/opcua/stack/core/security/SecurityPolicy;
|
||||
public final fun getUsername ()Lspace/kscience/controls/opcua/client/MiloUsername;
|
||||
public final fun setEndpointUrl (Ljava/lang/String;)V
|
||||
public final fun setSecurityPolicy (Lorg/eclipse/milo/opcua/stack/core/security/SecurityPolicy;)V
|
||||
public final fun setUsername (Lspace/kscience/controls/opcua/client/MiloUsername;)V
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/client/MiloConfiguration$Companion : space/kscience/dataforge/meta/SchemeSpec {
|
||||
}
|
||||
|
||||
public abstract class space/kscience/controls/opcua/client/MiloIdentity : space/kscience/dataforge/meta/Scheme {
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/client/MiloUsername : space/kscience/controls/opcua/client/MiloIdentity {
|
||||
public static final field Companion Lspace/kscience/controls/opcua/client/MiloUsername$Companion;
|
||||
public fun <init> ()V
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
public final fun getUsername ()Ljava/lang/String;
|
||||
public final fun setPassword (Ljava/lang/String;)V
|
||||
public final fun setUsername (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/client/MiloUsername$Companion : space/kscience/dataforge/meta/SchemeSpec {
|
||||
}
|
||||
|
||||
public abstract interface class space/kscience/controls/opcua/client/OpcUaDevice : space/kscience/controls/api/Device {
|
||||
public abstract fun getClient ()Lorg/eclipse/milo/opcua/sdk/client/OpcUaClient;
|
||||
}
|
||||
|
||||
public class space/kscience/controls/opcua/client/OpcUaDeviceBySpec : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/opcua/client/OpcUaDevice {
|
||||
public fun <init> (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/controls/opcua/client/MiloConfiguration;Lspace/kscience/dataforge/context/Context;)V
|
||||
public synthetic fun <init> (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/controls/opcua/client/MiloConfiguration;Lspace/kscience/dataforge/context/Context;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun close ()V
|
||||
public fun getClient ()Lorg/eclipse/milo/opcua/sdk/client/OpcUaClient;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/client/OpcUaDeviceKt {
|
||||
public static final fun opcDouble (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;D)Lkotlin/properties/ReadWriteProperty;
|
||||
public static synthetic fun opcDouble$default (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;DILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty;
|
||||
public static final fun opcInt (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;D)Lkotlin/properties/ReadWriteProperty;
|
||||
public static synthetic fun opcInt$default (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;DILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty;
|
||||
public static final fun opcString (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;D)Lkotlin/properties/ReadWriteProperty;
|
||||
public static synthetic fun opcString$default (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;DILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/server/DeviceNameSpace : org/eclipse/milo/opcua/sdk/server/api/ManagedNamespaceWithLifecycle {
|
||||
public static final field Companion Lspace/kscience/controls/opcua/server/DeviceNameSpace$Companion;
|
||||
public static final field NAMESPACE_URI Ljava/lang/String;
|
||||
public fun <init> (Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer;Lspace/kscience/controls/manager/DeviceManager;)V
|
||||
public final fun getDeviceManager ()Lspace/kscience/controls/manager/DeviceManager;
|
||||
public fun onDataItemsCreated (Ljava/util/List;)V
|
||||
public fun onDataItemsDeleted (Ljava/util/List;)V
|
||||
public fun onDataItemsModified (Ljava/util/List;)V
|
||||
public fun onMonitoringModeChanged (Ljava/util/List;)V
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/server/DeviceNameSpace$Companion {
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/server/DeviceNameSpaceKt {
|
||||
public static final fun get (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/api/PropertyDescriptor;)Lspace/kscience/dataforge/meta/Meta;
|
||||
public static final fun read (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/api/PropertyDescriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static final fun serveDevices (Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer;Lspace/kscience/controls/manager/DeviceManager;)Lspace/kscience/controls/opcua/server/DeviceNameSpace;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/opcua/server/ServerUtilsKt {
|
||||
public static final fun OpcUaServer (Lkotlin/jvm/functions/Function1;)Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer;
|
||||
public static final fun endpoint (Lorg/eclipse/milo/opcua/sdk/server/api/config/OpcUaServerConfigBuilder;Lkotlin/jvm/functions/Function1;)V
|
||||
}
|
||||
|
41
controls-opcua/build.gradle.kts
Normal file
41
controls-opcua/build.gradle.kts
Normal file
@ -0,0 +1,41 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
A client and server connectors for OPC-UA via Eclipse Milo
|
||||
""".trimIndent()
|
||||
|
||||
val ktorVersion: String by rootProject.extra
|
||||
|
||||
val miloVersion: String = "0.6.10"
|
||||
|
||||
dependencies {
|
||||
api(projects.controlsCore)
|
||||
api(spclibs.kotlinx.coroutines.jdk8)
|
||||
|
||||
api("org.eclipse.milo:sdk-client:$miloVersion")
|
||||
api("org.eclipse.milo:bsd-parser:$miloVersion")
|
||||
api("org.eclipse.milo:sdk-server:$miloVersion")
|
||||
|
||||
testImplementation(spclibs.kotlinx.coroutines.test)
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = Maturity.EXPERIMENTAL
|
||||
|
||||
feature("opcuaClient", ref = "src/main/kotlin/space/kscience/controls/opcua/client"){
|
||||
"""
|
||||
Connect a Controls-kt as a client to OPC UA server
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
feature("opcuaServer", ref = "src/main/kotlin/space/kscience/controls/opcua/server"){
|
||||
"""
|
||||
Create an OPC UA server on top of Controls-kt device (or device hub)
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package space.kscience.controls.opcua.client
|
||||
|
||||
import kotlinx.datetime.toJavaInstant
|
||||
import kotlinx.datetime.toKotlinInstant
|
||||
import org.eclipse.milo.opcua.binaryschema.AbstractCodec
|
||||
import org.eclipse.milo.opcua.binaryschema.parser.BsdParser
|
||||
import org.eclipse.milo.opcua.stack.core.UaSerializationException
|
||||
import org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamDecoder
|
||||
import org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamEncoder
|
||||
import org.eclipse.milo.opcua.stack.core.serialization.SerializationContext
|
||||
import org.eclipse.milo.opcua.stack.core.serialization.codecs.OpcUaBinaryDataTypeCodec
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.*
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.*
|
||||
import org.opcfoundation.opcua.binaryschema.EnumeratedType
|
||||
import org.opcfoundation.opcua.binaryschema.StructuredType
|
||||
import space.kscience.controls.misc.instant
|
||||
import space.kscience.controls.misc.toMeta
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import java.util.*
|
||||
|
||||
|
||||
public class MetaBsdParser : BsdParser() {
|
||||
override fun getEnumCodec(enumeratedType: EnumeratedType): OpcUaBinaryDataTypeCodec<*> {
|
||||
return MetaEnumCodec()
|
||||
}
|
||||
|
||||
override fun getStructCodec(structuredType: StructuredType): OpcUaBinaryDataTypeCodec<*> {
|
||||
return MetaStructureCodec(structuredType)
|
||||
}
|
||||
}
|
||||
|
||||
internal class MetaEnumCodec : OpcUaBinaryDataTypeCodec<Number> {
|
||||
override fun getType(): Class<Number> {
|
||||
return Number::class.java
|
||||
}
|
||||
|
||||
@Throws(UaSerializationException::class)
|
||||
override fun encode(
|
||||
context: SerializationContext,
|
||||
encoder: OpcUaBinaryStreamEncoder,
|
||||
value: Number
|
||||
) {
|
||||
encoder.writeInt32(value.toInt())
|
||||
}
|
||||
|
||||
@Throws(UaSerializationException::class)
|
||||
override fun decode(
|
||||
context: SerializationContext,
|
||||
decoder: OpcUaBinaryStreamDecoder
|
||||
): Number {
|
||||
return decoder.readInt32()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun opcToMeta(value: Any?): Meta = when (value) {
|
||||
null -> Meta(Null)
|
||||
is Meta -> value
|
||||
is Value -> Meta(value)
|
||||
is Number -> when (value) {
|
||||
is UByte -> Meta(value.toShort().asValue())
|
||||
is UShort -> Meta(value.toInt().asValue())
|
||||
is UInteger -> Meta(value.toLong().asValue())
|
||||
is ULong -> Meta(value.toBigInteger().asValue())
|
||||
else -> Meta(value.asValue())
|
||||
}
|
||||
is Boolean -> Meta(value.asValue())
|
||||
is String -> Meta(value.asValue())
|
||||
is Char -> Meta(value.toString().asValue())
|
||||
is DateTime -> value.javaInstant.toKotlinInstant().toMeta()
|
||||
is UUID -> Meta(value.toString().asValue())
|
||||
is QualifiedName -> Meta {
|
||||
"namespaceIndex" put value.namespaceIndex
|
||||
"name" put value.name?.asValue()
|
||||
}
|
||||
is LocalizedText -> Meta {
|
||||
"locale" put value.locale?.asValue()
|
||||
"text" put value.text?.asValue()
|
||||
}
|
||||
is DataValue -> Meta {
|
||||
"value" put opcToMeta(value.value) // need SerializationContext to do that properly
|
||||
value.statusCode?.value?.let { "status" put Meta(it.asValue()) }
|
||||
value.sourceTime?.javaInstant?.let { "sourceTime" put it.toKotlinInstant().toMeta() }
|
||||
value.sourcePicoseconds?.let { "sourcePicoseconds" put Meta(it.asValue()) }
|
||||
value.serverTime?.javaInstant?.let { "serverTime" put it.toKotlinInstant().toMeta() }
|
||||
value.serverPicoseconds?.let { "serverPicoseconds" put Meta(it.asValue()) }
|
||||
}
|
||||
is ByteString -> Meta(value.bytesOrEmpty().asValue())
|
||||
is XmlElement -> Meta(value.fragment?.asValue() ?: Null)
|
||||
is NodeId -> Meta(value.toParseableString().asValue())
|
||||
is ExpandedNodeId -> Meta(value.toParseableString().asValue())
|
||||
is StatusCode -> Meta(value.value.asValue())
|
||||
//is ExtensionObject -> value.decode(client.getDynamicSerializationContext())
|
||||
else -> error("Could not create Meta for value: $value")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* based on https://github.com/eclipse/milo/blob/master/opc-ua-stack/bsd-parser-gson/src/main/java/org/eclipse/milo/opcua/binaryschema/gson/JsonStructureCodec.java
|
||||
*/
|
||||
internal class MetaStructureCodec(
|
||||
structuredType: StructuredType?
|
||||
) : AbstractCodec<Meta, Meta>(structuredType) {
|
||||
|
||||
override fun getType(): Class<Meta> = Meta::class.java
|
||||
|
||||
override fun createStructure(name: String, members: LinkedHashMap<String, Meta>): Meta = Meta {
|
||||
members.forEach { (property: String, value: Meta?) ->
|
||||
setMeta(Name.parse(property), value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun opcUaToMemberTypeScalar(name: String, value: Any?, typeName: String): Meta = opcToMeta(value)
|
||||
|
||||
override fun opcUaToMemberTypeArray(name: String, values: Any?, typeName: String): Meta = if (values == null) {
|
||||
Meta(Null)
|
||||
} else {
|
||||
// This is a bit array...
|
||||
when (values) {
|
||||
is DoubleArray -> Meta(values.asValue())
|
||||
is FloatArray -> Meta(values.asValue())
|
||||
is IntArray -> Meta(values.asValue())
|
||||
is ByteArray -> Meta(values.asValue())
|
||||
is ShortArray -> Meta(values.asValue())
|
||||
is Array<*> -> Meta {
|
||||
setIndexed(Name.parse(name), values.map { opcUaToMemberTypeScalar(name, it, typeName) })
|
||||
}
|
||||
is Number -> Meta(values.asValue())
|
||||
else -> error("Could not create Meta for value: $values")
|
||||
}
|
||||
}
|
||||
|
||||
override fun memberTypeToOpcUaScalar(member: Meta?, typeName: String): Any? =
|
||||
if (member == null || member.isEmpty()) {
|
||||
null
|
||||
} else when (typeName) {
|
||||
"Boolean" -> member.boolean
|
||||
"SByte" -> member.value?.numberOrNull?.toByte()
|
||||
"Int16" -> member.value?.numberOrNull?.toShort()
|
||||
"Int32" -> member.value?.numberOrNull?.toInt()
|
||||
"Int64" -> member.value?.numberOrNull?.toLong()
|
||||
"Byte" -> member.value?.numberOrNull?.toShort()?.let { Unsigned.ubyte(it) }
|
||||
"UInt16" -> member.value?.numberOrNull?.toInt()?.let { Unsigned.ushort(it) }
|
||||
"UInt32" -> member.value?.numberOrNull?.toLong()?.let { Unsigned.uint(it) }
|
||||
"UInt64" -> member.value?.numberOrNull?.toLong()?.let { Unsigned.ulong(it) }
|
||||
"Float" -> member.value?.numberOrNull?.toFloat()
|
||||
"Double" -> member.value?.numberOrNull?.toDouble()
|
||||
"String" -> member.string
|
||||
"DateTime" -> DateTime(member.instant().toJavaInstant())
|
||||
"Guid" -> member.string?.let { UUID.fromString(it) }
|
||||
"ByteString" -> member.value?.list?.let { list ->
|
||||
ByteString(list.map { it.number.toByte() }.toByteArray())
|
||||
}
|
||||
"XmlElement" -> member.string?.let { XmlElement(it) }
|
||||
"NodeId" -> member.string?.let { NodeId.parse(it) }
|
||||
"ExpandedNodeId" -> member.string?.let { ExpandedNodeId.parse(it) }
|
||||
"StatusCode" -> member.long?.let { StatusCode(it) }
|
||||
"QualifiedName" -> QualifiedName(
|
||||
member["namespaceIndex"].int ?: 0,
|
||||
member["name"].string
|
||||
)
|
||||
"LocalizedText" -> LocalizedText(
|
||||
member["locale"].string,
|
||||
member["text"].string
|
||||
)
|
||||
else -> member.toString()
|
||||
}
|
||||
|
||||
override fun memberTypeToOpcUaArray(member: Meta, typeName: String): Any = if ("Bit" == typeName) {
|
||||
member.value?.int ?: error("Meta node does not contain int value")
|
||||
} else {
|
||||
when (typeName) {
|
||||
"SByte" -> member.value?.list?.map { it.number.toByte() }?.toByteArray() ?: emptyArray<Byte>()
|
||||
"Int16" -> member.value?.list?.map { it.number.toShort() }?.toShortArray() ?: emptyArray<Short>()
|
||||
"Int32" -> member.value?.list?.map { it.number.toInt() }?.toIntArray() ?: emptyArray<Int>()
|
||||
"Int64" -> member.value?.list?.map { it.number.toLong() }?.toLongArray() ?: emptyArray<Long>()
|
||||
"Byte" -> member.value?.list?.map {
|
||||
Unsigned.ubyte(it.number.toShort())
|
||||
}?.toTypedArray() ?: emptyArray<UByte>()
|
||||
"UInt16" -> member.value?.list?.map {
|
||||
Unsigned.ushort(it.number.toInt())
|
||||
}?.toTypedArray() ?: emptyArray<UShort>()
|
||||
"UInt32" -> member.value?.list?.map {
|
||||
Unsigned.uint(it.number.toLong())
|
||||
}?.toTypedArray() ?: emptyArray<UInteger>()
|
||||
"UInt64" -> member.value?.list?.map {
|
||||
Unsigned.ulong(it.number.toLong())
|
||||
}?.toTypedArray() ?: emptyArray<kotlin.ULong>()
|
||||
"Float" -> member.value?.list?.map { it.number.toFloat() }?.toFloatArray() ?: emptyArray<Float>()
|
||||
"Double" -> member.value?.list?.map { it.number.toDouble() }?.toDoubleArray() ?: emptyArray<Double>()
|
||||
else -> member.getIndexed(Meta.JSON_ARRAY_KEY.asName()).map {
|
||||
memberTypeToOpcUaScalar(it.value, typeName)
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMembers(value: Meta): Map<String, Meta> = value.items.mapKeys { it.toString() }
|
||||
}
|
||||
|
||||
public fun Variant.toMeta(serializationContext: SerializationContext): Meta = (value as? ExtensionObject)?.let {
|
||||
it.decode(serializationContext) as Meta
|
||||
} ?: opcToMeta(value)
|
||||
|
||||
//public fun Meta.toVariant(): Variant = if (items.isEmpty()) {
|
||||
// Variant(value?.value)
|
||||
//} else {
|
||||
// TODO()
|
||||
//}
|
@ -0,0 +1,126 @@
|
||||
package space.kscience.controls.opcua.client
|
||||
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.*
|
||||
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaSerializer
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
/**
|
||||
* An OPC-UA device backed by Eclipse Milo client
|
||||
*/
|
||||
public interface OpcUaDevice : Device {
|
||||
/**
|
||||
* The OPC-UA client initialized on first use
|
||||
*/
|
||||
public val client: OpcUaClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Read OPC-UA value with timestamp
|
||||
* @param T the type of property to read. The value is coerced to it.
|
||||
*/
|
||||
public suspend inline fun <reified T: Any> OpcUaDevice.readOpcWithTime(
|
||||
nodeId: NodeId,
|
||||
converter: MetaConverter<T>,
|
||||
magAge: Double = 500.0
|
||||
): Pair<T, DateTime> {
|
||||
val data = client.readValue(magAge, TimestampsToReturn.Server, nodeId).await()
|
||||
val time = data.serverTime ?: error("No server time provided")
|
||||
val meta: Meta = when (val content = data.value.value) {
|
||||
is T -> return content to time
|
||||
is Meta -> content
|
||||
is ExtensionObject -> content.decode(client.dynamicSerializationContext) as Meta
|
||||
else -> error("Incompatible OPC property value $content")
|
||||
}
|
||||
|
||||
val res: T = converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||
return res to time
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and coerce value from OPC-UA
|
||||
*/
|
||||
public suspend inline fun <reified T> OpcUaDevice.readOpc(
|
||||
nodeId: NodeId,
|
||||
converter: MetaConverter<T>,
|
||||
magAge: Double = 500.0
|
||||
): T {
|
||||
val data: DataValue = client.readValue(magAge, TimestampsToReturn.Neither, nodeId).await()
|
||||
|
||||
val content = data.value.value
|
||||
if(content is T) return content
|
||||
val meta: Meta = when (content) {
|
||||
is Meta -> content
|
||||
//Always decode string as Json meta
|
||||
is String -> Json.decodeFromString(MetaSerializer, content)
|
||||
is Number -> Meta(content)
|
||||
is Boolean -> Meta(content)
|
||||
//content is ExtensionObject -> (content as ExtensionObject).decode(client.dynamicSerializationContext) as Meta
|
||||
else -> error("Incompatible OPC property value $content")
|
||||
}
|
||||
|
||||
return converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||
}
|
||||
|
||||
public suspend inline fun <reified T> OpcUaDevice.writeOpc(
|
||||
nodeId: NodeId,
|
||||
converter: MetaConverter<T>,
|
||||
value: T
|
||||
): StatusCode {
|
||||
val meta = converter.objectToMeta(value)
|
||||
return client.writeValue(nodeId, DataValue(Variant(meta))).await()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A device-bound OPC-UA property. Does not trigger device properties change.
|
||||
*/
|
||||
public inline fun <reified T> OpcUaDevice.opc(
|
||||
nodeId: NodeId,
|
||||
converter: MetaConverter<T>,
|
||||
magAge: Double = 500.0
|
||||
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking {
|
||||
readOpc(nodeId, converter, magAge)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
launch {
|
||||
writeOpc(nodeId, converter, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mutable OPC-UA based [Double] property in a device spec
|
||||
*/
|
||||
public fun OpcUaDevice.opcDouble(
|
||||
nodeId: NodeId,
|
||||
magAge: Double = 1.0
|
||||
): ReadWriteProperty<Any?, Double> = opc<Double>(nodeId, MetaConverter.double, magAge)
|
||||
|
||||
/**
|
||||
* Register a mutable OPC-UA based [Int] property in a device spec
|
||||
*/
|
||||
public fun OpcUaDevice.opcInt(
|
||||
nodeId: NodeId,
|
||||
magAge: Double = 1.0
|
||||
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
|
||||
|
||||
/**
|
||||
* Register a mutable OPC-UA based [String] property in a device spec
|
||||
*/
|
||||
public fun OpcUaDevice.opcString(
|
||||
nodeId: NodeId,
|
||||
magAge: Double = 1.0
|
||||
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)
|
@ -0,0 +1,70 @@
|
||||
package space.kscience.controls.opcua.client
|
||||
|
||||
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder
|
||||
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider
|
||||
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.spec.DeviceBySpec
|
||||
import space.kscience.controls.spec.DeviceSpec
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.meta.*
|
||||
|
||||
|
||||
public sealed class MiloIdentity : Scheme()
|
||||
|
||||
public class MiloUsername : MiloIdentity() {
|
||||
|
||||
public var username: String by string { error("Username not defined") }
|
||||
public var password: String by string { error("Password not defined") }
|
||||
|
||||
public companion object : SchemeSpec<MiloUsername>(::MiloUsername)
|
||||
}
|
||||
|
||||
//public class MiloKeyPair : MiloIdentity() {
|
||||
//
|
||||
// public companion object : SchemeSpec<MiloUsername>(::MiloUsername)
|
||||
//}
|
||||
|
||||
public class MiloConfiguration : Scheme() {
|
||||
|
||||
public var endpointUrl: String by string { error("Endpoint url is not defined") }
|
||||
|
||||
public var username: MiloUsername? by specOrNull(MiloUsername)
|
||||
|
||||
public var securityPolicy: SecurityPolicy by enum(SecurityPolicy.None)
|
||||
|
||||
internal fun configureClient(builder: OpcUaClientConfigBuilder) {
|
||||
username?.let {
|
||||
builder.setIdentityProvider(UsernameProvider(it.username, it.password))
|
||||
}
|
||||
}
|
||||
|
||||
public companion object : SchemeSpec<MiloConfiguration>(::MiloConfiguration)
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of [DeviceBySpec] that includes OPC-UA client
|
||||
*/
|
||||
public open class OpcUaDeviceBySpec<D : Device>(
|
||||
spec: DeviceSpec<D>,
|
||||
config: MiloConfiguration,
|
||||
context: Context = Global,
|
||||
) : OpcUaDevice, DeviceBySpec<D>(spec, context, config.meta) {
|
||||
|
||||
override val client: OpcUaClient by lazy {
|
||||
context.createOpcUaClient(
|
||||
config.endpointUrl,
|
||||
securityPolicy = config.securityPolicy,
|
||||
opcClientConfig = { config.configureClient(this) }
|
||||
).apply {
|
||||
connect().get()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
client.disconnect()
|
||||
super<DeviceBySpec>.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package space.kscience.controls.opcua.client
|
||||
|
||||
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder
|
||||
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider
|
||||
import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator
|
||||
import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager
|
||||
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint
|
||||
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
internal fun <T : Any> T?.toOptional(): Optional<T> = Optional.ofNullable(this)
|
||||
|
||||
|
||||
internal fun Context.createOpcUaClient(
|
||||
endpointUrl: String, //"opc.tcp://localhost:12686/milo"
|
||||
securityPolicy: SecurityPolicy = SecurityPolicy.Basic256Sha256,
|
||||
endpointFilter: (EndpointDescription?) -> Boolean = { securityPolicy.uri == it?.securityPolicyUri },
|
||||
opcClientConfig: OpcUaClientConfigBuilder.() -> Unit,
|
||||
): OpcUaClient {
|
||||
|
||||
val securityTempDir: Path = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security")
|
||||
Files.createDirectories(securityTempDir)
|
||||
check(Files.exists(securityTempDir)) { "Unable to create security dir: $securityTempDir" }
|
||||
|
||||
val pkiDir: Path = securityTempDir.resolve("pki")
|
||||
logger.info { "Milo client security dir: ${securityTempDir.toAbsolutePath()}" }
|
||||
logger.info { "Security pki dir: ${pkiDir.toAbsolutePath()}" }
|
||||
|
||||
//val loader: KeyStoreLoader = KeyStoreLoader().load(securityTempDir)
|
||||
val trustListManager = DefaultTrustListManager(pkiDir.toFile())
|
||||
val certificateValidator = DefaultClientCertificateValidator(trustListManager)
|
||||
|
||||
return OpcUaClient.create(
|
||||
endpointUrl,
|
||||
{ endpoints: List<EndpointDescription?> ->
|
||||
endpoints.firstOrNull(endpointFilter).toOptional()
|
||||
}
|
||||
) { configBuilder: OpcUaClientConfigBuilder ->
|
||||
configBuilder
|
||||
.setApplicationName(LocalizedText.english("Controls-kt"))
|
||||
.setApplicationUri("urn:space.kscience:controls:opcua")
|
||||
// .setKeyPair(loader.getClientKeyPair())
|
||||
// .setCertificate(loader.getClientCertificate())
|
||||
// .setCertificateChain(loader.getClientCertificateChain())
|
||||
.setCertificateValidator(certificateValidator)
|
||||
.setIdentityProvider(AnonymousProvider())
|
||||
.setRequestTimeout(uint(5000))
|
||||
.apply(opcClientConfig)
|
||||
.build()
|
||||
}
|
||||
// .apply {
|
||||
// addSessionInitializer(DataTypeDictionarySessionInitializer(MetaBsdParser()))
|
||||
// }
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
package space.kscience.controls.opcua.server
|
||||
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.toJavaInstant
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.eclipse.milo.opcua.sdk.core.AccessLevel
|
||||
import org.eclipse.milo.opcua.sdk.core.Reference
|
||||
import org.eclipse.milo.opcua.sdk.server.Lifecycle
|
||||
import org.eclipse.milo.opcua.sdk.server.OpcUaServer
|
||||
import org.eclipse.milo.opcua.sdk.server.api.DataItem
|
||||
import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle
|
||||
import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaNode
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode
|
||||
import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel
|
||||
import org.eclipse.milo.opcua.stack.core.AttributeId
|
||||
import org.eclipse.milo.opcua.stack.core.Identifiers
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.DeviceHub
|
||||
import space.kscience.controls.api.PropertyDescriptor
|
||||
import space.kscience.controls.api.onPropertyChange
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaSerializer
|
||||
import space.kscience.dataforge.meta.ValueType
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.plus
|
||||
|
||||
|
||||
public operator fun Device.get(propertyDescriptor: PropertyDescriptor): Meta? = getProperty(propertyDescriptor.name)
|
||||
|
||||
public suspend fun Device.read(propertyDescriptor: PropertyDescriptor): Meta = readProperty(propertyDescriptor.name)
|
||||
|
||||
/*
|
||||
https://github.com/eclipse/milo/blob/master/milo-examples/server-examples/src/main/java/org/eclipse/milo/examples/server/ExampleNamespace.java
|
||||
*/
|
||||
|
||||
public class DeviceNameSpace(
|
||||
server: OpcUaServer,
|
||||
public val deviceManager: DeviceManager
|
||||
) : ManagedNamespaceWithLifecycle(server, NAMESPACE_URI) {
|
||||
|
||||
private val subscription = SubscriptionModel(server, this)
|
||||
|
||||
init {
|
||||
lifecycleManager.addLifecycle(subscription)
|
||||
|
||||
lifecycleManager.addStartupTask {
|
||||
nodeContext.registerHub(deviceManager, Name.EMPTY)
|
||||
}
|
||||
|
||||
lifecycleManager.addLifecycle(object : Lifecycle {
|
||||
override fun startup() {
|
||||
server.addressSpaceManager.register(this@DeviceNameSpace)
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
server.addressSpaceManager.unregister(this@DeviceNameSpace)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun UaFolderNode.registerDeviceNodes(deviceName: Name, device: Device) {
|
||||
val nodes = device.propertyDescriptors.associate { descriptor ->
|
||||
val propertyName = descriptor.name
|
||||
|
||||
|
||||
val node: UaVariableNode = UaVariableNode.UaVariableNodeBuilder(nodeContext).apply {
|
||||
//for now, use DF paths as ids
|
||||
nodeId = newNodeId("${deviceName.tokens.joinToString("/")}/$propertyName")
|
||||
when {
|
||||
descriptor.readable && descriptor.writable -> {
|
||||
setAccessLevel(AccessLevel.READ_WRITE)
|
||||
setUserAccessLevel(AccessLevel.READ_WRITE)
|
||||
}
|
||||
descriptor.writable -> {
|
||||
setAccessLevel(AccessLevel.WRITE_ONLY)
|
||||
setUserAccessLevel(AccessLevel.WRITE_ONLY)
|
||||
}
|
||||
descriptor.readable -> {
|
||||
setAccessLevel(AccessLevel.READ_ONLY)
|
||||
setUserAccessLevel(AccessLevel.READ_ONLY)
|
||||
}
|
||||
else -> {
|
||||
setAccessLevel(AccessLevel.NONE)
|
||||
setUserAccessLevel(AccessLevel.NONE)
|
||||
}
|
||||
}
|
||||
|
||||
browseName = newQualifiedName(propertyName)
|
||||
displayName = LocalizedText.english(propertyName)
|
||||
dataType = if (descriptor.metaDescriptor.children.isNotEmpty()) {
|
||||
Identifiers.String
|
||||
} else when (descriptor.metaDescriptor.valueTypes?.first()) {
|
||||
null, ValueType.STRING, ValueType.NULL -> Identifiers.String
|
||||
ValueType.NUMBER -> Identifiers.Number
|
||||
ValueType.BOOLEAN -> Identifiers.Boolean
|
||||
ValueType.LIST -> Identifiers.ArrayItemType
|
||||
}
|
||||
|
||||
|
||||
setTypeDefinition(Identifiers.BaseDataVariableType)
|
||||
}.build()
|
||||
|
||||
|
||||
device[descriptor]?.toOpc(sourceTime = null, serverTime = null)?.let {
|
||||
node.value = it
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to node value changes
|
||||
*/
|
||||
node.addAttributeObserver { _: UaNode, attributeId: AttributeId, value: Any ->
|
||||
if (attributeId == AttributeId.Value) {
|
||||
val meta: Meta = when (value) {
|
||||
is Meta -> value
|
||||
is Boolean -> Meta(value)
|
||||
is Number -> Meta(value)
|
||||
is String -> Json.decodeFromString(MetaSerializer, value)
|
||||
else -> return@addAttributeObserver //TODO("other types not implemented")
|
||||
}
|
||||
deviceManager.context.launch {
|
||||
device.writeProperty(propertyName, meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeManager.addNode(node)
|
||||
addOrganizes(node)
|
||||
propertyName to node
|
||||
}
|
||||
|
||||
//Subscribe on properties updates
|
||||
device.onPropertyChange {
|
||||
nodes[property]?.let { node ->
|
||||
val sourceTime = time?.let { DateTime(it.toJavaInstant()) }
|
||||
node.value = value.toOpc(sourceTime = sourceTime)
|
||||
}
|
||||
}
|
||||
//recursively add sub-devices
|
||||
if (device is DeviceHub) {
|
||||
nodeContext.registerHub(device, deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun UaNodeContext.registerHub(hub: DeviceHub, namePrefix: Name) {
|
||||
hub.devices.forEach { (deviceName, device) ->
|
||||
val tokenAsString = deviceName.toString()
|
||||
val deviceFolder = UaFolderNode(
|
||||
this,
|
||||
newNodeId(tokenAsString),
|
||||
newQualifiedName(tokenAsString),
|
||||
LocalizedText.english(tokenAsString)
|
||||
)
|
||||
deviceFolder.addReference(
|
||||
Reference(
|
||||
deviceFolder.nodeId,
|
||||
Identifiers.Organizes,
|
||||
Identifiers.ObjectsFolder.expanded(),
|
||||
false
|
||||
)
|
||||
)
|
||||
deviceFolder.registerDeviceNodes(namePrefix + deviceName, device)
|
||||
this.nodeManager.addNode(deviceFolder)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDataItemsCreated(dataItems: List<DataItem?>?) {
|
||||
subscription.onDataItemsCreated(dataItems)
|
||||
}
|
||||
|
||||
override fun onDataItemsModified(dataItems: List<DataItem?>?) {
|
||||
subscription.onDataItemsModified(dataItems)
|
||||
}
|
||||
|
||||
override fun onDataItemsDeleted(dataItems: List<DataItem?>?) {
|
||||
subscription.onDataItemsDeleted(dataItems)
|
||||
}
|
||||
|
||||
override fun onMonitoringModeChanged(monitoredItems: List<MonitoredItem?>?) {
|
||||
subscription.onMonitoringModeChanged(monitoredItems)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val NAMESPACE_URI: String = "urn:space:kscience:controls:opcua:server"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve devices from [deviceManager] as OPC-UA
|
||||
*/
|
||||
public fun OpcUaServer.serveDevices(deviceManager: DeviceManager): DeviceNameSpace =
|
||||
DeviceNameSpace(this, deviceManager).apply { startup() }
|
@ -0,0 +1,35 @@
|
||||
package space.kscience.controls.opcua.server
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant
|
||||
import space.kscience.dataforge.meta.*
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Convert Meta to OPC data value using
|
||||
*/
|
||||
internal fun Meta.toOpc(
|
||||
statusCode: StatusCode = StatusCode.GOOD,
|
||||
sourceTime: DateTime? = null,
|
||||
serverTime: DateTime? = null
|
||||
): DataValue {
|
||||
val variant: Variant = if (isLeaf) {
|
||||
when (value?.type) {
|
||||
null, ValueType.NULL -> Variant.NULL_VALUE
|
||||
ValueType.NUMBER -> Variant(value!!.number)
|
||||
ValueType.STRING -> Variant(value!!.string)
|
||||
ValueType.BOOLEAN -> Variant(value!!.boolean)
|
||||
ValueType.LIST -> if (value!!.list.all { it.type == ValueType.NUMBER }) {
|
||||
Variant(value!!.doubleArray.toTypedArray())
|
||||
} else {
|
||||
Variant(value!!.stringList.toTypedArray())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Variant(Json.encodeToString(MetaSerializer,this))
|
||||
}
|
||||
return DataValue(variant, statusCode, sourceTime,serverTime ?: DateTime(Instant.now()))
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package space.kscience.controls.opcua.server
|
||||
|
||||
import org.eclipse.milo.opcua.sdk.core.AccessLevel
|
||||
import org.eclipse.milo.opcua.sdk.core.Reference
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaNode
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext
|
||||
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode
|
||||
import org.eclipse.milo.opcua.stack.core.Identifiers
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.*
|
||||
|
||||
|
||||
internal fun UaNode.inverseReferenceTo(targetNodeId: NodeId, typeId: NodeId) {
|
||||
addReference(
|
||||
Reference(
|
||||
nodeId,
|
||||
typeId,
|
||||
targetNodeId.expanded(),
|
||||
Reference.Direction.INVERSE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
internal fun NodeId.resolve(child: String): NodeId {
|
||||
val id = this.identifier.toString()
|
||||
return NodeId(this.namespaceIndex, "$id/$child")
|
||||
}
|
||||
|
||||
|
||||
internal fun UaNodeContext.addVariableNode(
|
||||
parentNodeId: NodeId,
|
||||
name: String,
|
||||
nodeId: NodeId = parentNodeId.resolve(name),
|
||||
dataTypeId: NodeId,
|
||||
value: Any,
|
||||
referenceTypeId: NodeId = Identifiers.HasComponent
|
||||
): UaVariableNode {
|
||||
|
||||
val variableNode: UaVariableNode = UaVariableNode.UaVariableNodeBuilder(this).apply {
|
||||
setNodeId(nodeId)
|
||||
setAccessLevel(AccessLevel.toValue(AccessLevel.READ_WRITE))
|
||||
setUserAccessLevel(AccessLevel.toValue(AccessLevel.READ_WRITE))
|
||||
setBrowseName(QualifiedName(parentNodeId.namespaceIndex, name))
|
||||
setDisplayName(LocalizedText.english(name))
|
||||
setDataType(dataTypeId)
|
||||
setTypeDefinition(Identifiers.BaseDataVariableType)
|
||||
setMinimumSamplingInterval(100.0)
|
||||
setValue(DataValue(Variant(value)))
|
||||
}.build()
|
||||
|
||||
// variableNode.filterChain.addFirst(AttributeLoggingFilter())
|
||||
|
||||
nodeManager.addNode(variableNode)
|
||||
|
||||
variableNode.inverseReferenceTo(
|
||||
parentNodeId,
|
||||
referenceTypeId
|
||||
)
|
||||
|
||||
return variableNode
|
||||
}
|
||||
//
|
||||
//fun UaNodeContext.addVariableNode(
|
||||
// parentNodeId: NodeId,
|
||||
// name: String,
|
||||
// nodeId: NodeId = parentNodeId.resolve(name),
|
||||
// dataType: BuiltinDataType = BuiltinDataType.Int32,
|
||||
// referenceTypeId: NodeId = Identifiers.HasComponent
|
||||
//): UaVariableNode = addVariableNode(
|
||||
// parentNodeId,
|
||||
// name,
|
||||
// nodeId,
|
||||
// dataType.nodeId,
|
||||
// dataType.defaultValue(),
|
||||
// referenceTypeId
|
||||
//)
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
package space.kscience.controls.opcua.server
|
||||
|
||||
import org.eclipse.milo.opcua.sdk.server.OpcUaServer
|
||||
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig
|
||||
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfigBuilder
|
||||
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration
|
||||
|
||||
public fun OpcUaServer(block: OpcUaServerConfigBuilder.() -> Unit): OpcUaServer {
|
||||
// .setProductUri(DemoServer.PRODUCT_URI)
|
||||
// .setApplicationUri("${DemoServer.APPLICATION_URI}:$applicationUuid")
|
||||
// .setApplicationName(LocalizedText.english("Eclipse Milo OPC UA Demo Server"))
|
||||
// .setBuildInfo(buildInfo())
|
||||
// .setTrustListManager(trustListManager)
|
||||
// .setCertificateManager(certificateManager)
|
||||
// .setCertificateValidator(certificateValidator)
|
||||
// .setIdentityValidator(identityValidator)
|
||||
// .setEndpoints(endpoints)
|
||||
// .setLimits(ServerLimits)
|
||||
|
||||
val config = OpcUaServerConfig.builder().apply(block)
|
||||
|
||||
return OpcUaServer(config.build())
|
||||
}
|
||||
|
||||
public fun OpcUaServerConfigBuilder.endpoint(block: EndpointConfiguration.Builder.() -> Unit) {
|
||||
val endpoint = EndpointConfiguration.Builder().apply(block).build()
|
||||
setEndpoints(setOf(endpoint))
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package space.kscience.controls.opcua.client
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.kscience.controls.spec.DeviceSpec
|
||||
import space.kscience.controls.spec.doubleProperty
|
||||
import space.kscience.controls.spec.read
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.test.Ignore
|
||||
|
||||
class OpcUaClientTest {
|
||||
class DemoOpcUaDevice(config: MiloConfiguration) : OpcUaDeviceBySpec<DemoOpcUaDevice>(DemoOpcUaDevice, config) {
|
||||
|
||||
//val randomDouble by opcDouble(NodeId(2, "Dynamic/RandomDouble"))
|
||||
|
||||
suspend fun readRandomDouble() = readOpc(NodeId(2, "Dynamic/RandomDouble"), MetaConverter.double)
|
||||
|
||||
|
||||
companion object : DeviceSpec<DemoOpcUaDevice>() {
|
||||
/**
|
||||
* Build a device. This is not a part of the specification
|
||||
*/
|
||||
fun build(): DemoOpcUaDevice {
|
||||
val config = MiloConfiguration {
|
||||
endpointUrl = "opc.tcp://milo.digitalpetri.com:62541/milo"
|
||||
}
|
||||
return DemoOpcUaDevice(config)
|
||||
}
|
||||
|
||||
val randomDouble by doubleProperty(read = DemoOpcUaDevice::readRandomDouble)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
@Ignore
|
||||
fun testReadDouble() = runTest {
|
||||
DemoOpcUaDevice.build().use{
|
||||
println(it.read(DemoOpcUaDevice.randomDouble))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
23
controls-pi/README.md
Normal file
23
controls-pi/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module controls-pi
|
||||
|
||||
Utils to work with controls-kt on Raspberry pi
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-pi:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-pi:0.2.0")
|
||||
}
|
||||
```
|
28
controls-pi/api/controls-pi.api
Normal file
28
controls-pi/api/controls-pi.api
Normal file
@ -0,0 +1,28 @@
|
||||
public final class space/kscience/controls/pi/PiPlugin : space/kscience/dataforge/context/AbstractPlugin {
|
||||
public static final field Companion Lspace/kscience/controls/pi/PiPlugin$Companion;
|
||||
public fun <init> ()V
|
||||
public final fun getPorts ()Lspace/kscience/controls/ports/Ports;
|
||||
public fun getTag ()Lspace/kscience/dataforge/context/PluginTag;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/pi/PiPlugin$Companion : space/kscience/dataforge/context/PluginFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/pi/PiPlugin;
|
||||
public fun getTag ()Lspace/kscience/dataforge/context/PluginTag;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/pi/PiSerialPort : space/kscience/controls/ports/AbstractPort {
|
||||
public static final field Companion Lspace/kscience/controls/pi/PiSerialPort$Companion;
|
||||
public fun <init> (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;)V
|
||||
public synthetic fun <init> (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun close ()V
|
||||
public final fun getSerialBuilder ()Lkotlin/jvm/functions/Function0;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/pi/PiSerialPort$Companion : space/kscience/controls/ports/PortFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/pi/PiSerialPort;
|
||||
}
|
||||
|
16
controls-pi/build.gradle.kts
Normal file
16
controls-pi/build.gradle.kts
Normal file
@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
Utils to work with controls-kt on Raspberry pi
|
||||
""".trimIndent()
|
||||
|
||||
dependencies{
|
||||
api(project(":controls-core"))
|
||||
api("com.pi4j:pi4j-ktx:2.4.0") // Kotlin DSL
|
||||
api("com.pi4j:pi4j-core:2.3.0")
|
||||
api("com.pi4j:pi4j-plugin-raspberrypi:2.3.0")
|
||||
api("com.pi4j:pi4j-plugin-pigpio:2.3.0")
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package space.kscience.controls.pi
|
||||
|
||||
import space.kscience.controls.ports.Ports
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
|
||||
public class PiPlugin : AbstractPlugin() {
|
||||
public val ports: Ports by require(Ports)
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
public companion object : PluginFactory<PiPlugin> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports.pi", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override fun build(context: Context, meta: Meta): PiPlugin = PiPlugin()
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package space.kscience.controls.pi
|
||||
|
||||
import com.pi4j.Pi4J
|
||||
import com.pi4j.io.serial.Baud
|
||||
import com.pi4j.io.serial.Serial
|
||||
import com.pi4j.io.serial.SerialConfigBuilder
|
||||
import com.pi4j.ktx.io.serial
|
||||
import kotlinx.coroutines.*
|
||||
import space.kscience.controls.ports.AbstractPort
|
||||
import space.kscience.controls.ports.Port
|
||||
import space.kscience.controls.ports.PortFactory
|
||||
import space.kscience.controls.ports.toArray
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.error
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.enum
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
public class PiSerialPort(
|
||||
context: Context,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
public val serialBuilder: () -> Serial,
|
||||
) : AbstractPort(context, coroutineContext) {
|
||||
|
||||
private val serial: Serial by lazy { serialBuilder() }
|
||||
|
||||
|
||||
private val listenerJob = this.scope.launch(Dispatchers.IO) {
|
||||
val buffer = ByteBuffer.allocate(1024)
|
||||
while (isActive) {
|
||||
try {
|
||||
val num = serial.read(buffer)
|
||||
if (num > 0) {
|
||||
receive(buffer.toArray(num))
|
||||
}
|
||||
if (num < 0) cancel("The input channel is exhausted")
|
||||
} catch (ex: Exception) {
|
||||
logger.error(ex) { "Channel read error" }
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
|
||||
serial.write(data)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
listenerJob.cancel()
|
||||
serial.close()
|
||||
}
|
||||
|
||||
public companion object : PortFactory {
|
||||
override val type: String get() = "pi"
|
||||
|
||||
public fun open(context: Context, device: String, block: SerialConfigBuilder.() -> Unit): PiSerialPort =
|
||||
PiSerialPort(context) {
|
||||
Pi4J.newAutoContext().serial(device, block)
|
||||
}
|
||||
|
||||
override fun build(context: Context, meta: Meta): Port = PiSerialPort(context) {
|
||||
val device: String = meta["device"].string ?: error("Device name not defined")
|
||||
val baudRate: Baud = meta["baudRate"].enum<Baud>() ?: Baud._9600
|
||||
Pi4J.newAutoContext().serial(device) {
|
||||
baud8N1(baudRate)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
23
controls-ports-ktor/README.md
Normal file
23
controls-ports-ktor/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module controls-ports-ktor
|
||||
|
||||
Implementation of byte ports on top os ktor-io asynchronous API
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-ports-ktor:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-ports-ktor:0.2.0")
|
||||
}
|
||||
```
|
47
controls-ports-ktor/api/controls-ports-ktor.api
Normal file
47
controls-ports-ktor/api/controls-ports-ktor.api
Normal file
@ -0,0 +1,47 @@
|
||||
public final class space/kscience/controls/ports/KtorPortsPlugin : space/kscience/dataforge/context/AbstractPlugin {
|
||||
public static final field Companion Lspace/kscience/controls/ports/KtorPortsPlugin$Companion;
|
||||
public fun <init> ()V
|
||||
public fun content (Ljava/lang/String;)Ljava/util/Map;
|
||||
public fun getTag ()Lspace/kscience/dataforge/context/PluginTag;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/ports/KtorPortsPlugin$Companion : space/kscience/dataforge/context/PluginFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/KtorPortsPlugin;
|
||||
public fun getTag ()Lspace/kscience/dataforge/context/PluginTag;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/ports/KtorTcpPort : space/kscience/controls/ports/AbstractPort, java/io/Closeable {
|
||||
public static final field Companion Lspace/kscience/controls/ports/KtorTcpPort$Companion;
|
||||
public fun close ()V
|
||||
public final fun getHost ()Ljava/lang/String;
|
||||
public final fun getPort ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/ports/KtorTcpPort$Companion : space/kscience/controls/ports/PortFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/ports/KtorTcpPort;
|
||||
public static synthetic fun open$default (Lspace/kscience/controls/ports/KtorTcpPort$Companion;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/ports/KtorTcpPort;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/ports/KtorUdpPort : space/kscience/controls/ports/AbstractPort, java/io/Closeable {
|
||||
public static final field Companion Lspace/kscience/controls/ports/KtorUdpPort$Companion;
|
||||
public fun close ()V
|
||||
public final fun getLocalHost ()Ljava/lang/String;
|
||||
public final fun getLocalPort ()Ljava/lang/Integer;
|
||||
public final fun getRemoteHost ()Ljava/lang/String;
|
||||
public final fun getRemotePort ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/ports/KtorUdpPort$Companion : space/kscience/controls/ports/PortFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/ports/KtorUdpPort;
|
||||
public static synthetic fun open$default (Lspace/kscience/controls/ports/KtorUdpPort$Companion;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/ports/KtorUdpPort;
|
||||
}
|
||||
|
21
controls-ports-ktor/build.gradle.kts
Normal file
21
controls-ports-ktor/build.gradle.kts
Normal file
@ -0,0 +1,21 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
Implementation of byte ports on top os ktor-io asynchronous API
|
||||
""".trimIndent()
|
||||
|
||||
val ktorVersion: String by rootProject.extra
|
||||
|
||||
dependencies {
|
||||
api(projects.controlsCore)
|
||||
api("io.ktor:ktor-network:$ktorVersion")
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = Maturity.PROTOTYPE
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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
|
||||
|
||||
public class KtorPortsPlugin : AbstractPlugin() {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when (target) {
|
||||
PortFactory.TYPE -> mapOf("tcp".asName() to KtorTcpPort, "udp".asName() to KtorUdpPort)
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<KtorPortsPlugin> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports.ktor", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override fun build(context: Context, meta: Meta): KtorPortsPlugin = KtorPortsPlugin()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +1,21 @@
|
||||
package hep.dataforge.control.ports
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.string
|
||||
import io.ktor.network.selector.ActorSelectorManager
|
||||
import io.ktor.network.sockets.aSocket
|
||||
import io.ktor.network.sockets.openReadChannel
|
||||
import io.ktor.network.sockets.openWriteChannel
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.ktor.utils.io.consumeEachBufferRange
|
||||
import io.ktor.utils.io.core.Closeable
|
||||
import io.ktor.utils.io.writeAvailable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.InetSocketAddress
|
||||
import space.kscience.dataforge.context.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
|
||||
|
||||
public class KtorTcpPort internal constructor(
|
||||
@ -24,13 +23,12 @@ public class KtorTcpPort internal constructor(
|
||||
public val host: String,
|
||||
public val port: Int,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
) : AbstractPort(context, coroutineContext), AutoCloseable {
|
||||
) : AbstractPort(context, coroutineContext), Closeable {
|
||||
|
||||
override fun toString(): String = "port[tcp:$host:$port]"
|
||||
|
||||
@OptIn(KtorExperimentalAPI::class)
|
||||
private val futureSocket = scope.async {
|
||||
aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress(host, port))
|
||||
aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(host, port)
|
||||
}
|
||||
|
||||
private val writeChannel = scope.async {
|
||||
@ -39,7 +37,7 @@ public class KtorTcpPort internal constructor(
|
||||
|
||||
private val listenerJob = scope.launch {
|
||||
val input = futureSocket.await().openReadChannel()
|
||||
input.consumeEachBufferRange { buffer, last ->
|
||||
input.consumeEachBufferRange { buffer, _ ->
|
||||
val array = ByteArray(buffer.remaining())
|
||||
buffer.get(array)
|
||||
receive(array)
|
||||
@ -57,7 +55,10 @@ public class KtorTcpPort internal constructor(
|
||||
super.close()
|
||||
}
|
||||
|
||||
public companion object: PortFactory {
|
||||
public companion object : PortFactory {
|
||||
|
||||
override val type: String = "tcp"
|
||||
|
||||
public fun open(
|
||||
context: Context,
|
||||
host: String,
|
||||
@ -67,7 +68,7 @@ public class KtorTcpPort internal constructor(
|
||||
return KtorTcpPort(context, host, port, coroutineContext)
|
||||
}
|
||||
|
||||
override fun invoke(meta: Meta, context: Context): Port {
|
||||
override fun build(context: Context, meta: Meta): Port {
|
||||
val host = meta["host"].string ?: "localhost"
|
||||
val port = meta["port"].int ?: error("Port value for TCP port is not defined in $meta")
|
||||
return open(context, host, port)
|
@ -0,0 +1,87 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import io.ktor.network.selector.ActorSelectorManager
|
||||
import io.ktor.network.sockets.InetSocketAddress
|
||||
import io.ktor.network.sockets.aSocket
|
||||
import io.ktor.network.sockets.openReadChannel
|
||||
import io.ktor.network.sockets.openWriteChannel
|
||||
import io.ktor.utils.io.consumeEachBufferRange
|
||||
import io.ktor.utils.io.core.Closeable
|
||||
import io.ktor.utils.io.writeAvailable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.number
|
||||
import space.kscience.dataforge.meta.string
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
public class KtorUdpPort internal constructor(
|
||||
context: Context,
|
||||
public val remoteHost: String,
|
||||
public val remotePort: Int,
|
||||
public val localPort: Int? = null,
|
||||
public val localHost: String = "localhost",
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
) : AbstractPort(context, coroutineContext), Closeable {
|
||||
|
||||
override fun toString(): String = "port[udp:$remoteHost:$remotePort]"
|
||||
|
||||
private val futureSocket = scope.async {
|
||||
aSocket(ActorSelectorManager(Dispatchers.IO)).udp().connect(
|
||||
remoteAddress = InetSocketAddress(remoteHost, remotePort),
|
||||
localAddress = localPort?.let { InetSocketAddress(localHost, localPort) }
|
||||
)
|
||||
}
|
||||
|
||||
private val writeChannel = scope.async {
|
||||
futureSocket.await().openWriteChannel(true)
|
||||
}
|
||||
|
||||
private val listenerJob = scope.launch {
|
||||
val input = futureSocket.await().openReadChannel()
|
||||
input.consumeEachBufferRange { buffer, _ ->
|
||||
val array = ByteArray(buffer.remaining())
|
||||
buffer.get(array)
|
||||
receive(array)
|
||||
isActive
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun write(data: ByteArray) {
|
||||
writeChannel.await().writeAvailable(data)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
listenerJob.cancel()
|
||||
futureSocket.cancel()
|
||||
super.close()
|
||||
}
|
||||
|
||||
public companion object : PortFactory {
|
||||
|
||||
override val type: String = "udp"
|
||||
|
||||
public fun open(
|
||||
context: Context,
|
||||
remoteHost: String,
|
||||
remotePort: Int,
|
||||
localPort: Int? = null,
|
||||
localHost: String = "localhost",
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
): KtorUdpPort {
|
||||
return KtorUdpPort(context, remoteHost, remotePort, localPort, localHost, coroutineContext)
|
||||
}
|
||||
|
||||
override fun build(context: Context, meta: Meta): Port {
|
||||
val remoteHost by meta.string { error("Remote host is not specified") }
|
||||
val remotePort by meta.number { error("Remote port is not specified") }
|
||||
val localHost: String? by meta.string()
|
||||
val localPort: Int? by meta.int()
|
||||
return open(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost")
|
||||
}
|
||||
}
|
||||
}
|
23
controls-serial/README.md
Normal file
23
controls-serial/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module controls-serial
|
||||
|
||||
Implementation of direct serial port communication with JSerialComm
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-serial:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-serial:0.2.0")
|
||||
}
|
||||
```
|
29
controls-serial/api/controls-serial.api
Normal file
29
controls-serial/api/controls-serial.api
Normal file
@ -0,0 +1,29 @@
|
||||
public final class space/kscience/controls/serial/JSerialCommPort : space/kscience/controls/ports/AbstractPort {
|
||||
public static final field Companion Lspace/kscience/controls/serial/JSerialCommPort$Companion;
|
||||
public fun <init> (Lspace/kscience/dataforge/context/Context;Lcom/fazecast/jSerialComm/SerialPort;Lkotlin/coroutines/CoroutineContext;)V
|
||||
public synthetic fun <init> (Lspace/kscience/dataforge/context/Context;Lcom/fazecast/jSerialComm/SerialPort;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun close ()V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/serial/JSerialCommPort$Companion : space/kscience/controls/ports/PortFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;IIIILkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/serial/JSerialCommPort;
|
||||
public static synthetic fun open$default (Lspace/kscience/controls/serial/JSerialCommPort$Companion;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;IIIILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/serial/JSerialCommPort;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/serial/SerialPortPlugin : space/kscience/dataforge/context/AbstractPlugin {
|
||||
public static final field Companion Lspace/kscience/controls/serial/SerialPortPlugin$Companion;
|
||||
public fun <init> ()V
|
||||
public fun content (Ljava/lang/String;)Ljava/util/Map;
|
||||
public fun getTag ()Lspace/kscience/dataforge/context/PluginTag;
|
||||
}
|
||||
|
||||
public final class space/kscience/controls/serial/SerialPortPlugin$Companion : space/kscience/dataforge/context/PluginFactory {
|
||||
public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object;
|
||||
public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/serial/SerialPortPlugin;
|
||||
public fun getTag ()Lspace/kscience/dataforge/context/PluginTag;
|
||||
}
|
||||
|
17
controls-serial/build.gradle.kts
Normal file
17
controls-serial/build.gradle.kts
Normal file
@ -0,0 +1,17 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = "Implementation of direct serial port communication with JSerialComm"
|
||||
|
||||
dependencies{
|
||||
api(project(":controls-core"))
|
||||
implementation("com.fazecast:jSerialComm:2.10.3")
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = Maturity.EXPERIMENTAL
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package space.kscience.controls.serial
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort
|
||||
import com.fazecast.jSerialComm.SerialPortDataListener
|
||||
import com.fazecast.jSerialComm.SerialPortEvent
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.ports.AbstractPort
|
||||
import space.kscience.controls.ports.Port
|
||||
import space.kscience.controls.ports.PortFactory
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.string
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* A port based on JSerialComm
|
||||
*/
|
||||
public class JSerialCommPort(
|
||||
context: Context,
|
||||
private val comPort: SerialPort,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
) : AbstractPort(context, coroutineContext) {
|
||||
|
||||
override fun toString(): String = "port[${comPort.descriptivePortName}]"
|
||||
|
||||
private val serialPortListener = object : SerialPortDataListener {
|
||||
override fun getListeningEvents(): Int = SerialPort.LISTENING_EVENT_DATA_AVAILABLE
|
||||
|
||||
override fun serialEvent(event: SerialPortEvent) {
|
||||
if (event.eventType == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
|
||||
scope.launch { receive(event.receivedData) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
comPort.addDataListener(serialPortListener)
|
||||
}
|
||||
|
||||
override suspend fun write(data: ByteArray) {
|
||||
comPort.writeBytes(data, data.size)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
comPort.removeDataListener()
|
||||
if (comPort.isOpen) {
|
||||
comPort.closePort()
|
||||
}
|
||||
super.close()
|
||||
}
|
||||
|
||||
public companion object : PortFactory {
|
||||
|
||||
override val type: String = "com"
|
||||
|
||||
|
||||
/**
|
||||
* Construct ComPort with given parameters
|
||||
*/
|
||||
public fun open(
|
||||
context: Context,
|
||||
portName: String,
|
||||
baudRate: Int = 9600,
|
||||
dataBits: Int = 8,
|
||||
stopBits: Int = SerialPort.ONE_STOP_BIT,
|
||||
parity: Int = SerialPort.NO_PARITY,
|
||||
coroutineContext: CoroutineContext = context.coroutineContext,
|
||||
): JSerialCommPort {
|
||||
val serialPort = SerialPort.getCommPort(portName).apply {
|
||||
setComPortParameters(baudRate, dataBits, stopBits, parity)
|
||||
openPort()
|
||||
}
|
||||
return JSerialCommPort(context, serialPort, coroutineContext)
|
||||
}
|
||||
|
||||
override fun build(context: Context, meta: Meta): Port {
|
||||
val name by meta.string { error("Serial port name not defined") }
|
||||
val baudRate by meta.int(9600)
|
||||
val dataBits by meta.int(8)
|
||||
val stopBits by meta.int(SerialPort.ONE_STOP_BIT)
|
||||
val parity by meta.int(SerialPort.NO_PARITY)
|
||||
return open(context, name, baudRate, dataBits, stopBits, parity)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package space.kscience.controls.serial
|
||||
|
||||
import space.kscience.controls.ports.PortFactory
|
||||
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
|
||||
|
||||
public class SerialPortPlugin : AbstractPlugin() {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when(target){
|
||||
PortFactory.TYPE -> mapOf(Name.EMPTY to JSerialCommPort)
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<SerialPortPlugin> {
|
||||
|
||||
override val tag: PluginTag = PluginTag("controls.ports.serial", group = PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override fun build(context: Context, meta: Meta): SerialPortPlugin = SerialPortPlugin()
|
||||
|
||||
}
|
||||
|
||||
}
|
23
controls-server/README.md
Normal file
23
controls-server/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module controls-server
|
||||
|
||||
A combined Magix event loop server with web server for visualization.
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-server:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-server:0.2.0")
|
||||
}
|
||||
```
|
9
controls-server/api/controls-server.api
Normal file
9
controls-server/api/controls-server.api
Normal file
@ -0,0 +1,9 @@
|
||||
public final class space/kscience/controls/server/DeviceWebServerKt {
|
||||
public static final fun deviceManagerModule (Lio/ktor/server/application/Application;Lspace/kscience/controls/manager/DeviceManager;[Lspace/kscience/magix/api/MagixFlowPlugin;Ljava/util/Collection;Ljava/lang/String;I)V
|
||||
public static synthetic fun deviceManagerModule$default (Lio/ktor/server/application/Application;Lspace/kscience/controls/manager/DeviceManager;[Lspace/kscience/magix/api/MagixFlowPlugin;Ljava/util/Collection;Ljava/lang/String;IILjava/lang/Object;)V
|
||||
public static final fun getWEB_SERVER_TARGET ()Lspace/kscience/dataforge/names/Name;
|
||||
public static final fun startDeviceServer (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/controls/manager/DeviceManager;ILjava/lang/String;)Lio/ktor/server/engine/ApplicationEngine;
|
||||
public static synthetic fun startDeviceServer$default (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/controls/manager/DeviceManager;ILjava/lang/String;ILjava/lang/Object;)Lio/ktor/server/engine/ApplicationEngine;
|
||||
public static final fun whenStarted (Lio/ktor/server/engine/ApplicationEngine;Lkotlin/jvm/functions/Function1;)V
|
||||
}
|
||||
|
29
controls-server/build.gradle.kts
Normal file
29
controls-server/build.gradle.kts
Normal file
@ -0,0 +1,29 @@
|
||||
import space.kscience.gradle.Maturity
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
description = """
|
||||
A combined Magix event loop server with web server for visualization.
|
||||
""".trimIndent()
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
val ktorVersion: String by rootProject.extra
|
||||
|
||||
dependencies {
|
||||
implementation(projects.controlsCore)
|
||||
implementation(projects.controlsPortsKtor)
|
||||
implementation(projects.magix.magixServer)
|
||||
implementation("io.ktor:ktor-server-cio:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-websockets:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-html-builder:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-status-pages:$ktorVersion")
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = Maturity.PROTOTYPE
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
package space.kscience.controls.server
|
||||
|
||||
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.cio.CIO
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.html.respondHtml
|
||||
import io.ktor.server.plugins.statuspages.StatusPages
|
||||
import io.ktor.server.request.receiveText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondRedirect
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.post
|
||||
import io.ktor.server.routing.route
|
||||
import io.ktor.server.routing.routing
|
||||
import io.ktor.server.util.getValue
|
||||
import io.ktor.server.websocket.WebSockets
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.html.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
import kotlinx.serialization.json.put
|
||||
import space.kscience.controls.api.DeviceMessage
|
||||
import space.kscience.controls.api.PropertyGetMessage
|
||||
import space.kscience.controls.api.PropertySetMessage
|
||||
import space.kscience.controls.api.getOrNull
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.controls.manager.respondHubMessage
|
||||
import space.kscience.dataforge.meta.toMeta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.magix.api.MagixEndpoint
|
||||
import space.kscience.magix.api.MagixFlowPlugin
|
||||
import space.kscience.magix.api.MagixMessage
|
||||
import space.kscience.magix.server.magixModule
|
||||
|
||||
|
||||
|
||||
private fun Application.deviceServerModule(manager: DeviceManager) {
|
||||
install(StatusPages) {
|
||||
exception<IllegalArgumentException> { call, cause ->
|
||||
call.respond(HttpStatusCode.BadRequest, cause.message ?: "")
|
||||
}
|
||||
}
|
||||
deviceManagerModule(manager)
|
||||
routing {
|
||||
get("/") {
|
||||
call.respondRedirect("/dashboard")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and start a web server for several devices
|
||||
*/
|
||||
public fun CoroutineScope.startDeviceServer(
|
||||
manager: DeviceManager,
|
||||
port: Int = MagixEndpoint.DEFAULT_MAGIX_HTTP_PORT,
|
||||
host: String = "localhost",
|
||||
): ApplicationEngine = embeddedServer(CIO, port, host, module = { deviceServerModule(manager) }).start()
|
||||
|
||||
public fun ApplicationEngine.whenStarted(callback: Application.() -> Unit) {
|
||||
environment.monitor.subscribe(ApplicationStarted, callback)
|
||||
}
|
||||
|
||||
|
||||
public val WEB_SERVER_TARGET: Name = "@webServer".asName()
|
||||
|
||||
public fun Application.deviceManagerModule(
|
||||
manager: DeviceManager,
|
||||
vararg plugins: MagixFlowPlugin,
|
||||
deviceNames: Collection<String> = manager.devices.keys.map { it.toString() },
|
||||
route: String = "/",
|
||||
buffer: Int = 100,
|
||||
) {
|
||||
if (pluginOrNull(WebSockets) == null) {
|
||||
install(WebSockets)
|
||||
}
|
||||
|
||||
// if (pluginOrNull(CORS) == null) {
|
||||
// install(CORS) {
|
||||
// anyHost()
|
||||
// }
|
||||
// }
|
||||
|
||||
routing {
|
||||
route(route) {
|
||||
get("dashboard") {
|
||||
call.respondHtml {
|
||||
head {
|
||||
title("Device server dashboard")
|
||||
}
|
||||
body {
|
||||
h1 {
|
||||
+"Device server dashboard"
|
||||
}
|
||||
deviceNames.forEach { deviceName ->
|
||||
val device =
|
||||
manager.getOrNull(deviceName)
|
||||
?: error("The device with name $deviceName not found in $manager")
|
||||
div {
|
||||
id = deviceName
|
||||
h2 { +deviceName }
|
||||
h3 { +"Properties" }
|
||||
ul {
|
||||
device.propertyDescriptors.forEach { property ->
|
||||
li {
|
||||
a(href = "../$deviceName/${property.name}/get") { +"${property.name}: " }
|
||||
code {
|
||||
+Json.encodeToString(property)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h3 { +"Actions" }
|
||||
ul {
|
||||
device.actionDescriptors.forEach { action ->
|
||||
li {
|
||||
+("${action.name}: ")
|
||||
code {
|
||||
+Json.encodeToString(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get("list") {
|
||||
call.respondJson {
|
||||
manager.devices.forEach { (name, device) ->
|
||||
put("target", name.toString())
|
||||
put("properties", buildJsonArray {
|
||||
device.propertyDescriptors.forEach { descriptor ->
|
||||
add(Json.encodeToJsonElement(descriptor))
|
||||
}
|
||||
})
|
||||
put("actions", buildJsonArray {
|
||||
device.actionDescriptors.forEach { actionDescriptor ->
|
||||
add(Json.encodeToJsonElement(actionDescriptor))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post("message") {
|
||||
val body = call.receiveText()
|
||||
val request: DeviceMessage = MagixEndpoint.magixJson.decodeFromString(DeviceMessage.serializer(), body)
|
||||
val response = manager.respondHubMessage(request)
|
||||
if (response != null) {
|
||||
call.respondMessage(response)
|
||||
} else {
|
||||
call.respondText("No response")
|
||||
}
|
||||
}
|
||||
|
||||
route("{target}") {
|
||||
//global route for the device
|
||||
|
||||
route("{property}") {
|
||||
get("get") {
|
||||
val target: String by call.parameters
|
||||
val property: String by call.parameters
|
||||
val request = PropertyGetMessage(
|
||||
sourceDevice = WEB_SERVER_TARGET,
|
||||
targetDevice = Name.parse(target),
|
||||
property = property,
|
||||
)
|
||||
|
||||
val response = manager.respondHubMessage(request)
|
||||
if (response != null) {
|
||||
call.respondMessage(response)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
post("set") {
|
||||
val target: String by call.parameters
|
||||
val property: String by call.parameters
|
||||
val body = call.receiveText()
|
||||
val json = Json.parseToJsonElement(body)
|
||||
|
||||
val request = PropertySetMessage(
|
||||
sourceDevice = WEB_SERVER_TARGET,
|
||||
targetDevice = Name.parse(target),
|
||||
property = property,
|
||||
value = json.toMeta()
|
||||
)
|
||||
|
||||
val response = manager.respondHubMessage(request)
|
||||
if (response != null) {
|
||||
call.respondMessage(response)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val magixFlow = MutableSharedFlow<MagixMessage>(
|
||||
buffer,
|
||||
extraBufferCapacity = buffer
|
||||
)
|
||||
|
||||
plugins.forEach {
|
||||
it.start(this, magixFlow)
|
||||
}
|
||||
magixModule(magixFlow)
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package space.kscience.controls.server
|
||||
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.response.respondText
|
||||
import kotlinx.serialization.json.JsonObjectBuilder
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import space.kscience.controls.api.DeviceMessage
|
||||
import space.kscience.magix.api.MagixEndpoint
|
||||
|
||||
|
||||
//internal fun Frame.toEnvelope(): Envelope {
|
||||
// return data.asBinary().readWith(TaggedEnvelopeFormat)
|
||||
//}
|
||||
//
|
||||
//internal fun Envelope.toFrame(): Frame {
|
||||
// val data = buildByteArray {
|
||||
// writeWith(TaggedEnvelopeFormat, this@toFrame)
|
||||
// }
|
||||
// return Frame.Binary(false, data)
|
||||
//}
|
||||
|
||||
internal suspend fun ApplicationCall.respondJson(builder: JsonObjectBuilder.() -> Unit) {
|
||||
val json = buildJsonObject(builder)
|
||||
respondText(json.toString(), contentType = ContentType.Application.Json)
|
||||
}
|
||||
|
||||
internal suspend fun ApplicationCall.respondMessage(message: DeviceMessage): Unit = respondText(
|
||||
MagixEndpoint.magixJson.encodeToString(DeviceMessage.serializer(), message),
|
||||
contentType = ContentType.Application.Json
|
||||
)
|
23
controls-storage/README.md
Normal file
23
controls-storage/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module controls-storage
|
||||
|
||||
An API for stand-alone Controls-kt device or a hub.
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-storage:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-storage:0.2.0")
|
||||
}
|
||||
```
|
27
controls-storage/build.gradle.kts
Normal file
27
controls-storage/build.gradle.kts
Normal file
@ -0,0 +1,27 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
|
||||
description = """
|
||||
An API for stand-alone Controls-kt device or a hub.
|
||||
""".trimIndent()
|
||||
|
||||
kscience{
|
||||
jvm()
|
||||
js()
|
||||
dependencies {
|
||||
api(projects.controlsCore)
|
||||
}
|
||||
dependencies(jvmMain){
|
||||
api(projects.magix.magixApi)
|
||||
api(projects.controlsMagix)
|
||||
api(projects.magix.magixServer)
|
||||
}
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = space.kscience.gradle.Maturity.PROTOTYPE
|
||||
}
|
23
controls-storage/controls-xodus/README.md
Normal file
23
controls-storage/controls-xodus/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module controls-xodus
|
||||
|
||||
An implementation of controls-storage on top of JetBrains Xodus.
|
||||
|
||||
## Usage
|
||||
|
||||
## Artifact:
|
||||
|
||||
The Maven coordinates of this project are `space.kscience:controls-xodus:0.2.0`.
|
||||
|
||||
**Gradle Kotlin DSL:**
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
//uncomment to access development builds
|
||||
//maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("space.kscience:controls-xodus:0.2.0")
|
||||
}
|
||||
```
|
23
controls-storage/controls-xodus/build.gradle.kts
Normal file
23
controls-storage/controls-xodus/build.gradle.kts
Normal file
@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.jvm")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
val xodusVersion: String by rootProject.extra
|
||||
|
||||
description = """
|
||||
An implementation of controls-storage on top of JetBrains Xodus.
|
||||
""".trimIndent()
|
||||
|
||||
dependencies {
|
||||
api(projects.controlsStorage)
|
||||
implementation("org.jetbrains.xodus:xodus-entity-store:$xodusVersion")
|
||||
// implementation("org.jetbrains.xodus:xodus-environment:$xodusVersion")
|
||||
// implementation("org.jetbrains.xodus:xodus-vfs:$xodusVersion")
|
||||
|
||||
testImplementation(spclibs.kotlinx.coroutines.test)
|
||||
}
|
||||
|
||||
readme{
|
||||
maturity = space.kscience.gradle.Maturity.PROTOTYPE
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package space.kscience.controls.xodus
|
||||
|
||||
import jetbrains.exodus.entitystore.Entity
|
||||
import jetbrains.exodus.entitystore.PersistentEntityStore
|
||||
import jetbrains.exodus.entitystore.PersistentEntityStores
|
||||
import jetbrains.exodus.entitystore.StoreTransaction
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.descriptors.serialDescriptor
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import space.kscience.controls.api.DeviceMessage
|
||||
import space.kscience.controls.storage.DeviceMessageStorage
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Factory
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.io.IOPlugin
|
||||
import space.kscience.dataforge.io.workDirectory
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.matches
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
|
||||
|
||||
internal fun StoreTransaction.writeMessage(message: DeviceMessage): Unit {
|
||||
val entity: Entity = newEntity(XodusDeviceMessageStorage.DEVICE_MESSAGE_ENTITY_TYPE)
|
||||
val json = Json.encodeToJsonElement(DeviceMessage.serializer(), message).jsonObject
|
||||
val type = json["type"]?.jsonPrimitive?.content ?: error("Message json representation must have type.")
|
||||
entity.setProperty("type", type)
|
||||
|
||||
message.sourceDevice?.let {
|
||||
entity.setProperty(DeviceMessage::sourceDevice.name, it.toString())
|
||||
}
|
||||
message.targetDevice?.let {
|
||||
entity.setProperty(DeviceMessage::targetDevice.name, it.toString())
|
||||
}
|
||||
message.time?.let {
|
||||
entity.setProperty(DeviceMessage::targetDevice.name, it.toString())
|
||||
}
|
||||
entity.setBlobString("json", Json.encodeToString(json))
|
||||
}
|
||||
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
private fun Entity.propertyMatchesName(propertyName: String, pattern: Name? = null) =
|
||||
pattern == null || getProperty(propertyName).toString().parseAsName().matches(pattern)
|
||||
|
||||
private fun Entity.timeInRange(range: ClosedRange<Instant>?): Boolean {
|
||||
if (range == null) return true
|
||||
val time: Instant? = getProperty(DeviceMessage::time.name)?.let { entityString ->
|
||||
Instant.parse(entityString.toString())
|
||||
}
|
||||
return time != null && time in range
|
||||
}
|
||||
|
||||
public class XodusDeviceMessageStorage(
|
||||
private val entityStore: PersistentEntityStore,
|
||||
) : DeviceMessageStorage, AutoCloseable {
|
||||
|
||||
override suspend fun write(event: DeviceMessage) {
|
||||
entityStore.executeInTransaction { txn ->
|
||||
txn.writeMessage(event)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun readAll(): List<DeviceMessage> = entityStore.computeInReadonlyTransaction { transaction ->
|
||||
transaction.sort(
|
||||
DEVICE_MESSAGE_ENTITY_TYPE,
|
||||
DeviceMessage::time.name,
|
||||
true
|
||||
).map {
|
||||
Json.decodeFromString(
|
||||
DeviceMessage.serializer(),
|
||||
it.getBlobString("json") ?: error("No json content found")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun read(
|
||||
eventType: String,
|
||||
range: ClosedRange<Instant>?,
|
||||
sourceDevice: Name?,
|
||||
targetDevice: Name?,
|
||||
): List<DeviceMessage> = entityStore.computeInReadonlyTransaction { transaction ->
|
||||
transaction.find(
|
||||
DEVICE_MESSAGE_ENTITY_TYPE,
|
||||
"type",
|
||||
eventType
|
||||
).asSequence().filter {
|
||||
it.timeInRange(range) &&
|
||||
it.propertyMatchesName(DeviceMessage::sourceDevice.name, sourceDevice) &&
|
||||
it.propertyMatchesName(DeviceMessage::targetDevice.name, targetDevice)
|
||||
}.map {
|
||||
Json.decodeFromString(
|
||||
DeviceMessage.serializer(),
|
||||
it.getBlobString("json") ?: error("No json content found")
|
||||
)
|
||||
}.sortedBy { it.time }.toList()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
entityStore.close()
|
||||
}
|
||||
|
||||
public companion object : Factory<XodusDeviceMessageStorage> {
|
||||
internal const val DEVICE_MESSAGE_ENTITY_TYPE = "controls-kt.message"
|
||||
public val XODUS_STORE_PROPERTY: Name = Name.of("xodus", "storagePath")
|
||||
|
||||
override fun build(context: Context, meta: Meta): XodusDeviceMessageStorage {
|
||||
val io = context.request(IOPlugin)
|
||||
val storePath = io.workDirectory.resolve(
|
||||
meta[XODUS_STORE_PROPERTY]?.string
|
||||
?: context.properties[XODUS_STORE_PROPERTY]?.string ?: "storage"
|
||||
)
|
||||
|
||||
val entityStore = PersistentEntityStores.newInstance(storePath.toFile())
|
||||
|
||||
return XodusDeviceMessageStorage(entityStore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all messages of given type
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
public suspend inline fun <reified T : DeviceMessage> XodusDeviceMessageStorage.query(
|
||||
range: ClosedRange<Instant>? = null,
|
||||
sourceDevice: Name? = null,
|
||||
targetDevice: Name? = null,
|
||||
): List<T> = read(serialDescriptor<T>().serialName, range, sourceDevice, targetDevice).map {
|
||||
//Check that all types are correct
|
||||
it as T
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import jetbrains.exodus.entitystore.PersistentEntityStores
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.datetime.Instant
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.kscience.controls.api.PropertyChangedMessage
|
||||
import space.kscience.controls.xodus.XodusDeviceMessageStorage
|
||||
import space.kscience.controls.xodus.query
|
||||
import space.kscience.controls.xodus.writeMessage
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import java.nio.file.Files
|
||||
|
||||
internal class PropertyHistoryTest {
|
||||
companion object {
|
||||
val storeFile = Files.createTempDirectory("controls-xodus").toFile()
|
||||
|
||||
|
||||
private val propertyChangedMessages = listOf(
|
||||
PropertyChangedMessage(
|
||||
"speed",
|
||||
Meta.EMPTY,
|
||||
time = Instant.fromEpochMilliseconds(1000),
|
||||
sourceDevice = Name.of("virtual-car")
|
||||
),
|
||||
PropertyChangedMessage(
|
||||
"acceleration",
|
||||
Meta.EMPTY,
|
||||
time = Instant.fromEpochMilliseconds(1500),
|
||||
sourceDevice = Name.of("virtual-car")
|
||||
),
|
||||
PropertyChangedMessage(
|
||||
"speed",
|
||||
Meta.EMPTY,
|
||||
time = Instant.fromEpochMilliseconds(2000),
|
||||
sourceDevice = Name.of("magix-virtual-car")
|
||||
)
|
||||
)
|
||||
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun createEntities() {
|
||||
PersistentEntityStores.newInstance(storeFile).use {
|
||||
it.executeInTransaction { transaction ->
|
||||
propertyChangedMessages.forEach { message ->
|
||||
transaction.writeMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@JvmStatic
|
||||
fun deleteDatabase() {
|
||||
storeFile.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun getPropertyHistoryTest() = runTest {
|
||||
PersistentEntityStores.newInstance(storeFile).use { entityStore ->
|
||||
XodusDeviceMessageStorage(entityStore).use { storage ->
|
||||
assertEquals(
|
||||
propertyChangedMessages[0],
|
||||
storage.query<PropertyChangedMessage>(
|
||||
sourceDevice = "virtual-car".asName()
|
||||
).first { it.property == "speed" }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package space.kscience.controls.storage
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import space.kscience.controls.api.DeviceMessage
|
||||
import space.kscience.dataforge.names.Name
|
||||
|
||||
/**
|
||||
* A storage for Controls-kt [DeviceMessage]
|
||||
*/
|
||||
public interface DeviceMessageStorage {
|
||||
public suspend fun write(event: DeviceMessage)
|
||||
|
||||
public suspend fun readAll(): List<DeviceMessage>
|
||||
|
||||
public suspend fun read(
|
||||
eventType: String,
|
||||
range: ClosedRange<Instant>? = null,
|
||||
sourceDevice: Name? = null,
|
||||
targetDevice: Name? = null,
|
||||
): List<DeviceMessage>
|
||||
|
||||
public fun close()
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package space.kscience.controls.storage
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import space.kscience.controls.api.DeviceMessage
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.controls.manager.hubMessageFlow
|
||||
import space.kscience.dataforge.context.Factory
|
||||
import space.kscience.dataforge.context.debug
|
||||
import space.kscience.dataforge.context.logger
|
||||
|
||||
//TODO replace by plugin?
|
||||
public fun DeviceManager.storage(
|
||||
factory: Factory<DeviceMessageStorage>,
|
||||
): DeviceMessageStorage = factory.build(context, meta)
|
||||
|
||||
/**
|
||||
* Begin to store DeviceMessages from this DeviceManager
|
||||
* @param factory factory that will be used for creating persistent entity store instance. DefaultPersistentStoreFactory by default.
|
||||
* DeviceManager's meta and context will be used for in invoke method.
|
||||
* @param filterCondition allow you to specify messages which we want to store. Always true by default.
|
||||
* @return Job which responsible for our storage
|
||||
*/
|
||||
public fun DeviceManager.storeMessages(
|
||||
factory: Factory<DeviceMessageStorage>,
|
||||
filterCondition: suspend (DeviceMessage) -> Boolean = { true },
|
||||
): Job {
|
||||
val storage = factory.build(context, meta)
|
||||
logger.debug { "Message storage with meta = $meta created" }
|
||||
|
||||
return hubMessageFlow(context).filter(filterCondition).onEach { message ->
|
||||
storage.write(message)
|
||||
}.onCompletion {
|
||||
storage.close()
|
||||
logger.debug { "Message storage closed" }
|
||||
}.launchIn(context)
|
||||
}
|
||||
|
||||
///**
|
||||
// * @return the list of deviceMessages that describes changes of specified property of specified device sorted by time
|
||||
// * @param sourceDeviceName a name of device, history of which property we want to get
|
||||
// * @param propertyName a name of property, history of which we want to get
|
||||
// * @param factory a factory that produce mongo clients
|
||||
// */
|
||||
//public suspend fun getPropertyHistory(
|
||||
// sourceDeviceName: String,
|
||||
// propertyName: String,
|
||||
// factory: Factory<EventStorage>,
|
||||
// meta: Meta = Meta.EMPTY,
|
||||
//): List<PropertyChangedMessage> {
|
||||
// return factory(meta).use {
|
||||
// it.getPropertyHistory(sourceDeviceName, propertyName)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//public enum class StorageKind {
|
||||
// DEVICE_HUB,
|
||||
// MAGIX_SERVER
|
||||
//}
|
||||
|
@ -0,0 +1,46 @@
|
||||
//package space.kscience.controls.storage
|
||||
//
|
||||
//import io.ktor.server.application.Application
|
||||
//import kotlinx.coroutines.InternalCoroutinesApi
|
||||
//import kotlinx.coroutines.flow.Flow
|
||||
//import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
//import kotlinx.coroutines.flow.filter
|
||||
//import kotlinx.coroutines.flow.onEach
|
||||
//import kotlinx.coroutines.job
|
||||
//import ru.mipt.npm.magix.server.GenericMagixMessage
|
||||
//import space.kscience.dataforge.context.Factory
|
||||
//import space.kscience.dataforge.meta.Meta
|
||||
//
|
||||
///**
|
||||
// * Asynchronous version of synchronous API, so for more details check relative docs
|
||||
// */
|
||||
//
|
||||
//internal fun Flow<GenericMagixMessage>.store(
|
||||
// client: EventStorage,
|
||||
// flowFilter: suspend (GenericMagixMessage) -> Boolean = { true },
|
||||
//) {
|
||||
// filter(flowFilter).onEach { message ->
|
||||
// client.storeMagixMessage(message)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///** Begin to store MagixMessages from certain flow
|
||||
// * @param flow flow of messages which we will store
|
||||
// * @param meta Meta which may have some configuration parameters for our storage and will be used in invoke method of factory
|
||||
// * @param factory factory that will be used for creating persistent entity store instance. DefaultPersistentStoreFactory by default.
|
||||
// * @param flowFilter allow you to specify messages which we want to store. Always true by default.
|
||||
// */
|
||||
//@OptIn(InternalCoroutinesApi::class)
|
||||
//public fun Application.store(
|
||||
// flow: MutableSharedFlow<GenericMagixMessage>,
|
||||
// factory: Factory<EventStorage>,
|
||||
// meta: Meta = Meta.EMPTY,
|
||||
// flowFilter: suspend (GenericMagixMessage) -> Boolean = { true },
|
||||
//) {
|
||||
// val client = factory(meta)
|
||||
//
|
||||
// flow.store(client, flowFilter)
|
||||
// coroutineContext.job.invokeOnCompletion(onCancelling = true) {
|
||||
// client.close()
|
||||
// }
|
||||
//}
|
@ -1,27 +0,0 @@
|
||||
plugins {
|
||||
id("ru.mipt.npm.mpp")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
val ktorVersion: String by rootProject.extra
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation(project(":dataforge-device-core"))
|
||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package hep.dataforge.control.client
|
||||
|
||||
import hep.dataforge.control.controllers.DeviceManager
|
||||
import hep.dataforge.control.controllers.DeviceMessage
|
||||
import hep.dataforge.control.controllers.respondMessage
|
||||
import hep.dataforge.meta.toJson
|
||||
import hep.dataforge.meta.toMeta
|
||||
import hep.dataforge.meta.wrap
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.Url
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/*
|
||||
{
|
||||
"id":"string|number[optional, but desired]",
|
||||
"parentId": "string|number[optional]",
|
||||
"target":"string[optional]",
|
||||
"origin":"string[required]",
|
||||
"user":"string[optional]",
|
||||
"action":"string[optional, default='heartbeat']",
|
||||
"payload":"object[optional]"
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1)
|
||||
*/
|
||||
public class MagixClient(
|
||||
private val manager: DeviceManager,
|
||||
private val postUrl: Url,
|
||||
private val inbox: Flow<JsonObject>
|
||||
): CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext = manager.context.coroutineContext + Job(manager.context.coroutineContext[Job])
|
||||
|
||||
private val client = HttpClient()
|
||||
|
||||
protected fun generateId(message: DeviceMessage, requestId: String?): String = if(requestId != null){
|
||||
"$requestId.response"
|
||||
} else{
|
||||
"df[${message.hashCode()}"
|
||||
}
|
||||
|
||||
private fun send(json: JsonObject) {
|
||||
launch {
|
||||
client.post<Unit>(postUrl) {
|
||||
this.contentType(ContentType.Application.Json)
|
||||
body = json.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun wrapMessage(message: DeviceMessage, requestId: String? = null): JsonObject = buildJsonObject {
|
||||
put("id", generateId(message, requestId))
|
||||
if (requestId != null) {
|
||||
put("parentId", requestId)
|
||||
}
|
||||
put("target", "magix")
|
||||
put("origin", "df")
|
||||
put("payload", message.config.toJson())
|
||||
}
|
||||
|
||||
|
||||
private val listenJob = launch {
|
||||
manager.controller.messageOutput().collect { message ->
|
||||
val json = wrapMessage(message)
|
||||
send(json)
|
||||
}
|
||||
}
|
||||
|
||||
private val respondJob = launch {
|
||||
inbox.collect { json ->
|
||||
val requestId = json["id"]?.jsonPrimitive?.content
|
||||
val payload = json["payload"]?.jsonObject
|
||||
//TODO analyze action
|
||||
|
||||
if(payload != null){
|
||||
val meta = payload.toMeta()
|
||||
val request = DeviceMessage.wrap(meta)
|
||||
val response = manager.respondMessage(request)
|
||||
send(wrapMessage(response,requestId))
|
||||
} else {
|
||||
TODO("process heartbeat and other system messages")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
package hep.dataforge.control.client
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.receive
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.HttpStatement
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
|
||||
suspend fun HttpClient.sse(address: String) = get<HttpStatement>(address).execute { response: HttpResponse ->
|
||||
// Response is not downloaded here.
|
||||
val channel = response.receive<ByteReadChannel>()
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
plugins {
|
||||
id("ru.mipt.npm.mpp")
|
||||
id("ru.mipt.npm.publish")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
|
||||
kscience {
|
||||
useCoroutines()
|
||||
useSerialization()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain{
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-io:$dataforgeVersion")
|
||||
}
|
||||
}
|
||||
jvmMain{
|
||||
dependencies{
|
||||
api("io.ktor:ktor-network:1.3.2")
|
||||
}
|
||||
}
|
||||
jsMain{
|
||||
dependencies{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package hep.dataforge.control.api
|
||||
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET
|
||||
import hep.dataforge.io.Envelope
|
||||
import hep.dataforge.io.EnvelopeBuilder
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.provider.Type
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.io.Closeable
|
||||
|
||||
/**
|
||||
* General interface describing a managed Device
|
||||
*/
|
||||
@Type(DEVICE_TARGET)
|
||||
public interface Device : Closeable, ContextAware {
|
||||
/**
|
||||
* 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>
|
||||
|
||||
/**
|
||||
* The supervisor scope encompassing all operations on a device. When canceled, cancels all running processes
|
||||
*/
|
||||
public val scope: CoroutineScope
|
||||
|
||||
/**
|
||||
* Register a new property change listener for this device.
|
||||
* [owner] is provided optionally in order for listener to be
|
||||
* easily removable
|
||||
*/
|
||||
public fun registerListener(listener: DeviceListener, owner: Any? = listener)
|
||||
|
||||
/**
|
||||
* Remove all listeners belonging to the specified owner
|
||||
*/
|
||||
public fun removeListeners(owner: Any?)
|
||||
|
||||
/**
|
||||
* Get the value of the property or throw error if property in not defined.
|
||||
* Suspend if property value is not available
|
||||
*/
|
||||
public suspend fun getProperty(propertyName: String): MetaItem<*>
|
||||
|
||||
/**
|
||||
* Invalidate property and force recalculate
|
||||
*/
|
||||
public suspend fun invalidateProperty(propertyName: String)
|
||||
|
||||
/**
|
||||
* 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 setProperty(propertyName: String, value: MetaItem<*>)
|
||||
|
||||
/**
|
||||
* 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(command: String, argument: MetaItem<*>? = null): MetaItem<*>?
|
||||
|
||||
/**
|
||||
*
|
||||
* A request with binary data or for binary response (or both). This request does not cover basic functionality like
|
||||
* [setProperty], [getProperty] or [execute] and not defined for a generic device.
|
||||
*
|
||||
*/
|
||||
public suspend fun respondWithData(request: Envelope): EnvelopeBuilder = error("No binary response defined")
|
||||
|
||||
override fun close() {
|
||||
scope.cancel("The device is closed")
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val DEVICE_TARGET: String = "device"
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun Device.execute(name: String, meta: Meta?): MetaItem<*>? = execute(name, meta?.let { MetaItem.NodeItem(it) })
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user