275 Commits

Author SHA1 Message Date
e729cb1a79 upfixes 2024-04-29 18:38:14 +06:00
8e7277df69 Merge branch 'dev' into support/update_dependencies 2024-04-29 18:09:22 +06:00
23bceed89d update dependencies 2024-04-29 17:22:56 +06:00
9eb583dfc6 Change plotAveragedDeviceProperty to show last value on miss. 2024-04-09 15:13:41 +03:00
977500223d Plc4X refactor 2024-04-07 10:07:23 +03:00
58675f72f5 Refactor ports 2024-03-31 16:33:22 +03:00
85c2910ee9 Refactor ports 2024-03-31 16:13:02 +03:00
d91296c47d Refactor load test 2024-03-25 15:48:23 +03:00
8965629151 complete dependencies extraction 2024-03-20 01:08:04 +06:00
9a40d4f340 exclude ktor/rsocket/dataforge versions 2024-03-20 00:48:26 +06:00
78dade4b49 PLC4x bindings 2024-03-18 17:18:31 +03:00
70ab60f98c fix plot extensions 2024-03-18 17:15:39 +03:00
53cc4dc0df PLC4x bindings 2024-03-18 09:34:14 +03:00
f28e9dc226 Update constructor api 2024-03-18 09:30:41 +03:00
29af4dfb2c Add heartbeat and watchdog 2024-03-12 22:26:43 +03:00
4835376c0d Add proper deviceName to in-memory property history 2024-03-06 18:55:11 +03:00
4639fdb558 update gradle wrapper 2024-03-06 00:21:55 +06:00
2946f23a4b Update readme and API 2024-03-04 16:02:50 +03:00
e8c6e90a0f Update readme and API 2024-03-04 15:58:53 +03:00
2a700a5a2a Migrate to DataForge 0.8.0 2024-03-04 15:24:27 +03:00
dbacdbc7cf Replace event controls-storage with async api 2024-03-04 12:47:40 +03:00
28ec2bc8b8 Add PropertyHistory API 2024-03-04 11:12:16 +03:00
cfd9eb053c Make DeviceMessage time mandatory 2024-03-04 11:11:56 +03:00
9edf3b13ef Remove unnecessary scope in hub message flow 2024-02-27 10:31:35 +03:00
57e9df140b Add utilities to work with remote devices 2024-02-19 15:10:51 +03:00
231f1bc858 Add special unsafe calls for DeviceClient to mirror safe device 2024-02-19 14:27:36 +03:00
8bd9bcc6a6 Fix bizzare NPE in context generation for DeviceClient.
Add test for remote client
2024-02-15 21:04:59 +03:00
b1121d61cb Allow controls magix endpoint to receive broadcast. 2024-02-05 14:08:15 +03:00
fa2414ef47 Add demo for device message listening 2024-02-02 16:04:41 +03:00
7579ddfad4 Quick fix for OPC us server 2023-12-28 22:40:58 +03:00
aa52b4b927 hub returns list of messages. 2023-12-28 21:09:23 +03:00
34f9108ef7 New builders for devices 2023-12-25 19:09:40 +03:00
bec075328b Make constructor device use context instead of device manager 2023-12-22 09:28:39 +03:00
701ea8cf57 Minor fixes to port implementations 2023-12-15 16:55:56 +03:00
5e64b79b77 Merge remote-tracking branch 'spc/dev' into dev 2023-12-13 20:24:43 +03:00
a12cf440e8 Finish migration to kotlinx-io 2023-12-13 20:20:03 +03:00
606c2cf5b1 Finish migration to kotlinx-io 2023-12-13 14:50:56 +03:00
fb03fcc982 Finish migration to kotlinx-io 2023-12-13 12:29:06 +03:00
cf129b6242 Migrate to DF 0.7 2023-12-12 09:59:52 +03:00
827eb6e4c1 minor update to constructor 2023-11-23 16:52:07 +03:00
81d6b672cf Add compose controls to pid simulator 2023-11-22 21:55:13 +03:00
07cc41c645 Automatic description generation for spec properties (JVM only) 2023-11-18 19:02:56 +03:00
0c647cff30 DeviceSpec properties no explicitly pass property name to getters and setters 2023-11-18 15:39:56 +03:00
b539c2046a DeviceSpec properties no explicitly pass property name to getters and setters 2023-11-18 14:49:23 +03:00
afee2f0a02 minor update to constructor 2023-11-17 12:22:06 +03:00
fb8ee59f14 replace debounce by sample 2023-11-08 22:33:49 +03:00
74301afb42 Return notifications about pid and drive updates. Introduce debounce 2023-11-08 22:28:26 +03:00
fe98a836f8 Update jupyter integration 2023-11-08 21:01:42 +03:00
0c128bce36 Merge remote-tracking branch 'spc/dev' into dev
# Conflicts:
#	demo/constructor/src/jvmMain/kotlin/main.kt
2023-11-08 15:31:55 +03:00
4e17c9051c Update jupyter integration 2023-11-08 15:31:12 +03:00
0f687c3c51 Update jupyter integration 2023-11-08 11:52:57 +03:00
53fc240c75 Test device constructor 2023-11-07 08:46:56 +03:00
825f1a4d04 Add DeviceConstructor 2023-11-06 16:46:16 +03:00
0443fdc3c0 Add fixed age plots for properties and states. 2023-11-06 11:39:56 +03:00
78b18ebda6 Move server to controls-vision 2023-11-05 10:18:26 +03:00
0e963a7b13 Simplify UI management in constructor 2023-11-05 09:47:58 +03:00
2698cee80b Remove automatic reads from virtual drive and pid 2023-11-02 15:36:10 +03:00
811477a636 add limit readers 2023-10-30 22:51:17 +03:00
984e7f12ef Add JVM application for constructor demo 2023-10-30 21:47:41 +03:00
1414cf5a2f implement constructor 2023-10-30 21:35:46 +03:00
1fcdbdc9f4 Update constructor 2023-10-28 14:18:00 +03:00
4f028ccee8 Lifecycle change 2023-10-27 10:57:46 +03:00
1619fdadf2 Refactoring. Developing composer 2023-10-25 22:31:36 +03:00
7f71d0c9e9 modbus registry to json rendering 2023-10-20 10:14:14 +03:00
290010fc8c Add writeable flag to mutable properties 2023-10-19 16:38:50 +03:00
80cc62e25b Merge remote-tracking branch 'spc/dev' into dev
# Conflicts:
#	demo/all-things/build.gradle.kts
2023-10-19 16:21:19 +03:00
f1b63c3951 Add buffer to device messages 2023-10-07 18:34:44 +03:00
01606af307 clientId -> unitId 2023-10-05 07:43:49 +03:00
2cc0a5bcbc Fixex in modbus and write-protection for same meta 2023-10-02 22:12:11 +03:00
efe9a2e842 Fixex in modbus and write-protection for same meta 2023-10-02 21:24:01 +03:00
34e7dd2c6d Add read-after-write for DeviceBase property writers 2023-09-24 13:29:15 +03:00
a337daee93 Add read-after-write for DeviceBase property writers 2023-09-24 13:21:01 +03:00
a51510606f add customizeable scopes to property listeners 2023-09-24 13:02:52 +03:00
aef94767c5 Fix all-things demo 2023-09-18 13:38:45 +03:00
8b6a6abd92 Update to PiPlugin logic 2023-09-18 09:00:04 +03:00
bc5037b256 fix dataforge version 2023-09-16 16:09:47 +03:00
036bef1adb fix dataforge version 2023-09-16 15:54:36 +03:00
cc36ef805b update version 2023-09-04 14:56:18 +03:00
0f610a5e19 Fix mass demo plot 2023-08-24 16:25:17 +03:00
4c93b5c9b3 Update readme 2023-08-23 16:37:35 +03:00
SPC-code
5b655a9354 Merge pull request #9 from SciProgCentre/dev
0.2.0
2023-08-23 16:27:13 +03:00
61af2780ed fix repositores 2023-08-23 16:23:27 +03:00
990e4794a1 All done for 0.2.0 release 2023-08-23 16:21:11 +03:00
641daec7e9 ControlsMagix refactor 2023-08-18 20:17:23 +03:00
a9f29f92ca Add magix registry access service 2023-08-18 13:01:46 +03:00
82dbb3a1f0 broadcast -> send 2023-08-16 20:27:23 +03:00
ce03540b37 Add magix message registry service 2023-08-16 18:26:21 +03:00
583fe5b54c Replace JSSC with JSerialComm 2023-08-16 10:42:44 +03:00
69617e73b4 Replace JSSC with JSerialComm 2023-08-16 10:37:02 +03:00
fa4f578a64 Update version 2023-08-03 19:21:48 +03:00
a172da0a3d Add abstraction for writeable magix history 2023-08-03 19:21:35 +03:00
dc2d0094fc add filter implementation for Json 2023-07-30 22:34:47 +03:00
d3d8413837 Add storage API test (inclomplete) 2023-07-29 18:08:25 +03:00
f9e20f8766 Make it buildable again 2023-07-29 11:52:04 +03:00
cf70339a9f Rename field in MagixMessage in accordance with https://github.com/waltz-controls/rfc/issues/12 2023-07-29 09:47:59 +03:00
870cb7ef40 [WIP] Storage API 2023-07-29 09:46:13 +03:00
fcabd9aed4 Documentation update and minor refactoring 2023-07-29 09:45:34 +03:00
405bcd6ba3 Add ZMQ context customization 2023-07-23 14:29:26 +03:00
6f5270ee37 Add MQTT topic specialization 2023-07-23 14:29:11 +03:00
c28944e10f Refactor connection infrastructure 2023-07-23 14:01:47 +03:00
4d4a9fba1c MQTT topic selectors 2023-06-29 09:19:05 +03:00
d1e9b0a5a5 Add device lifecycle state 2023-06-10 16:55:00 +03:00
d0d8869a1e Eternal happiness 2023-06-10 16:28:11 +03:00
20951a0b0f Modbus revision acording to modern standards 2023-06-10 16:17:54 +03:00
33e1aafa01 WIP fixing angles 2023-06-08 10:03:41 +03:00
bfeeed00d5 process images cleanup 2023-06-05 17:12:01 +03:00
166cc03fe2 Add modbus client 2023-06-04 21:18:42 +03:00
2aa26ea802 Update UDP ports design. Rename ktor ports module. 2023-06-04 11:23:26 +03:00
76cec0469f add useProperty subscription 2023-05-30 20:23:43 +03:00
2191bb7a77 Update visualization 2023-05-30 15:02:09 +03:00
9c39f44897 fix async port receive 2023-05-27 22:55:21 +03:00
22359a5570 Remove non-blocking mode for ports to avoid CPU clogging. 2023-05-27 22:44:30 +03:00
ced42779be Fix UDP connection 2023-05-27 21:36:46 +03:00
ab1a478867 Add publishing 2023-05-27 19:50:48 +03:00
7b792afd7a Fix nullability in actions 2023-05-25 10:07:51 +03:00
25d7fe0a8e Update actions signatures to avoid unnecessary nulls 2023-05-25 09:51:27 +03:00
b7be570271 Fix property listener 2023-05-24 09:56:02 +03:00
2a966d8cb3 Minor refactor of property listeners 2023-05-24 09:37:56 +03:00
0612c6e3a2 Modbus registry validation and print 2023-05-21 17:36:26 +03:00
b8a82feed0 Modbus-based implementation of controller 2023-05-21 16:53:42 +03:00
e7d02be849 Add Pi serial port 2023-05-19 21:55:21 +03:00
a5a2edc81a Add Pi serial port 2023-05-19 20:27:08 +03:00
28cb9af267 [WIP] PI 2023-05-18 09:50:39 +03:00
a6bf9b8db6 Generalize nio ports architecture. Add Udp(datagram) port 2023-05-18 09:30:26 +03:00
Alexander Nozik
64e240f6c2 Merge SCI-MR-188: dev 2023-05-12 10:36:42 +00:00
5e91ec9a97 update version 2023-05-12 13:35:34 +03:00
1d7403ef30 Update to stable DataForge 2023-05-09 22:50:35 +03:00
aabf2b85a4 Documentation and DeviceSpec fix 2023-05-08 21:25:25 +03:00
7103786ec9 Stress test demo 2023-05-08 15:39:34 +03:00
53e506893f Add RPC for devices 2023-05-07 21:04:08 +03:00
4c33c16c94 Fix demo 2023-05-07 18:03:46 +03:00
b66e23cca6 Clean up property access syntax 2023-05-07 16:39:58 +03:00
691b2ae67a Clean up property access syntax 2023-05-07 11:13:42 +03:00
86273101b6 Change command for CI 2023-05-03 08:51:19 +00:00
fd1194205c change docker image for CI 2023-05-03 08:50:52 +00:00
96305b1be6 Update space.kts JVM version 2023-05-03 08:16:46 +00:00
8cd191bb0d Documentation update 2023-05-03 11:05:54 +03:00
e7cfb1d2ba Add opcua test 2023-04-16 12:29:24 +03:00
197675fc15 Add MQTT prototype (not tested) 2023-04-15 20:00:47 +03:00
SPC-code
631eb73d23 Merge pull request #8 from SciProgCentre/dev
Dev
2023-03-04 16:47:54 +03:00
00c66da847 Update pressure sensor demo 2023-03-02 10:07:54 +03:00
ba84553be5 Add port demo. Nullable properties 2023-03-01 20:59:18 +03:00
5b16b07172 Fix build 2023-02-26 22:07:19 +03:00
3ef471d49e Move ZMQ to a separate module 2023-02-26 20:23:30 +03:00
fca718cfc4 Move ZMQ to a separate module 2023-02-25 18:15:19 +03:00
2a386568f9 Refactor magix server flow plugins 2023-02-25 17:45:09 +03:00
fe24c0ee31 Add magix-rfc as a submodule 2023-02-25 10:55:21 +03:00
68805d6f42 Refactor onOpen in device spec 2023-02-25 10:53:53 +03:00
a0fd8913eb Update versions 2023-02-18 20:05:26 +03:00
dc6847196d Updated dependencies 2022-08-10 19:20:06 +03:00
619c2c280d Hotfix for jdkRelease problem 2022-08-02 10:12:16 +03:00
4a09e66c42 continue package refactor 2022-08-02 09:46:31 +03:00
c22902cc91 Updata to DataForge 0.6 2022-08-02 09:37:36 +03:00
bd29789545 Change package name to space.kscience 2022-08-02 09:10:02 +03:00
20345f846b Add rabbitMQ connector (untested) 2022-06-06 14:10:48 +03:00
535e44286c Add port configuration to magix server 2022-06-06 10:57:24 +03:00
cfaeb964e7 Add benchmark demo. Fix some issues with RSocket 2022-06-05 19:06:50 +03:00
025a444db8 Fix demos and project structure 2022-06-05 12:26:34 +03:00
879073e339 Add native rsocket-tcp 2022-06-05 10:28:46 +03:00
07adf143cf Remove generic from MagixMessage 2022-06-04 15:46:13 +03:00
eb7507191e Refactor xodus storage for local history 2022-06-02 09:21:07 +03:00
b6f3769529 Refactor xodus storage for local history 2022-06-01 10:11:12 +03:00
ba4d2abd27 Add native targets 2022-05-24 21:28:42 +03:00
3d0b888c11 Update dependencies 2022-05-23 23:30:38 +03:00
2e7eefeba9 rename methods 2022-01-18 18:39:19 +03:00
427ecbf91a Refactor storage 2022-01-18 18:25:40 +03:00
786e1637b4 Fix ktor imports 2022-01-18 17:37:25 +03:00
Atos1337
0c4c2e4cc0 Fix issues 2022-01-17 12:19:35 +03:00
Atos1337
23e821d2c2 Merge remote-tracking branch 'origin/victorsam/finish-hostory-access' into victorsam/finish-hostory-access
# Conflicts:
#	controls-mongo/build.gradle.kts
#	controls-mongo/src/main/kotlin/ru/mipt/npm/controls/mongo/connections.kt
#	controls-xodus/src/main/kotlin/ru/mipt/npm/controls/xodus/connections.kt
#	controls-xodus/src/main/kotlin/ru/mipt/npm/controls/xodus/util/queries.kt
#	demo/car/src/main/kotlin/ru/mipt/npm/controls/demo/car/VirtualCarController.kt
2022-01-15 16:01:50 +03:00
Atos1337
21c13ce3af Update propertyHistoryTest for xodus 2022-01-15 16:00:23 +03:00
Atos1337
cbdd6a477b Refactor asynchronous storage api 2022-01-15 15:42:26 +03:00
Atos1337
89bb132e36 Refactor synchronous storage api 2022-01-14 22:00:52 +03:00
Atos1337
3ebbec35fe Add some docs 2022-01-09 12:48:26 +03:00
Atos1337
fe1575c5ee Add getPropertyHistory for xodus 2022-01-09 12:48:26 +03:00
Atos1337
f977aca237 Add storeInMongo for magix server 2022-01-09 12:48:26 +03:00
Atos1337
0c7cd951aa Make this work 2022-01-09 12:48:26 +03:00
798c8eb4ef Merge remote-tracking branch 'space/dev' into dev
# Conflicts:
#	gradle/wrapper/gradle-wrapper.properties
2021-12-24 20:52:40 +03:00
29c0291fa3 Add details for device description message.
Update build plugin and kotlin.
2021-12-24 20:52:04 +03:00
Atos1337
7ba36f2eb1 Add some docs 2021-12-16 23:14:32 +03:00
Atos1337
476dbfdeaf Add getPropertyHistory for xodus 2021-12-16 18:18:47 +03:00
Atos1337
0c4d2fc9e1 Add storeInMongo for magix server 2021-12-16 17:25:35 +03:00
Atos1337
dec45b6050 Make this work 2021-12-16 16:54:48 +03:00
cdd1286365 Merge CONTROLS-MR-11: Try to add xodus dependency 2021-12-15 12:28:25 +00:00
76846a50cd Some refactoring 2021-12-11 17:59:19 +03:00
Atos1337
f880b2d637 Move to xodus-serialization 2021-12-04 02:15:57 +03:00
Atos1337
e07780e7da Add entityStoreFactory and refactor mongo/xodus connections.kt 2021-12-04 02:10:10 +03:00
Atos1337
b79ad40a9b Fix some issues 2021-12-04 00:52:22 +03:00
Atos1337
cfd7bce2f3 Add some tests for encoding/decoding 2021-12-04 00:35:12 +03:00
Atos1337
e793cdefee Add more extensions for encoder and decoder 2021-12-03 23:55:43 +03:00
Atos1337
f77f6b7211 Add encoding and decoding to/from entity by json encoding/decoding 2021-12-03 23:29:13 +03:00
5da1559882 Architecture rework 2021-11-27 13:37:39 +03:00
Atos1337
312cd06706 Add mongo module 2021-11-27 00:52:14 +03:00
Atos1337
3639783b4e Add connection to xodus from magix server 2021-11-26 17:26:41 +03:00
Atos1337
0b14c7ed7f Make it work 2021-11-26 16:25:09 +03:00
85f6b81334 Architecture rework 2021-11-20 13:13:32 +03:00
Atos1337
099649f090 Add fromTo util function for storeTransacton 2021-11-19 23:03:54 +03:00
Atos1337
53f9af3ba4 Add converters from Entity and ConvertersTest 2021-11-19 22:15:50 +03:00
Atos1337
1d8cc33c91 Refactor connection to magix 2021-11-15 17:18:06 +03:00
Atos1337
a1c3902b92 Add entity store at controller 2021-11-15 16:38:28 +03:00
Atos1337
81a1afa2b7 Add converters for PropertyChangedMessage and add overload for magix connection 2021-11-15 15:35:51 +03:00
Atos1337
817d32a7e9 Try to add xodus dependency 2021-11-15 14:40:19 +03:00
df6defd637 add duplicating local filter for RSocketMagixEndpoint 2021-11-13 13:25:06 +03:00
Atos1337
039e0d083b Refactor MagixVirtualCar.kt 2021-11-06 09:12:19 +03:00
Atos1337
dcd496660c Implement MagixVirtualCar 2021-11-04 15:52:49 +03:00
Atos1337
289ff6ffd0 Add magix for debugging and moved DeviceSpec to IVirtualCar 2021-11-02 22:21:32 +03:00
4dc33c012d Refactor car demo. Add meta property. 2021-10-30 13:08:45 +03:00
Atos1337
c1a1b6e696 Merge remote-tracking branch 'origin/victorsam/demo' into victorsam/demo
# Conflicts:
#	demo/src/main/kotlin/ru/mipt/npm/controls/demo/virtual_car/VirtualCar.kt
#	demo/src/main/kotlin/ru/mipt/npm/controls/demo/virtual_car/VirtualCarController.kt
2021-10-28 11:17:45 +03:00
Atos1337
357fcec4fe Fix issues 2021-10-28 11:16:19 +03:00
Atos1337
14015178ab Create Coordinates class instead of Pair and refactor state update 2021-10-27 13:55:15 +03:00
Atos1337
bd23ca1129 Add location property in virtual car 2021-10-27 13:55:15 +03:00
Atos1337
24c5188c30 Change VirtualCar state and add controller 2021-10-27 13:55:15 +03:00
Atos1337
77496aedec Implement VirtualCar 2021-10-27 13:55:14 +03:00
7b56436983 Add open to device initializer 2021-10-23 20:00:00 +03:00
ed2a2a29af Remove stand-alone properties and use specs instead 2021-10-23 19:44:13 +03:00
Atos1337
418a97f99a Create Coordinates class instead of Pair and refactor state update 2021-10-23 12:12:19 +03:00
Alexander Nozik
a20fb97c02 Merge pull request #6 from mipt-npm/dev
Dev
2021-10-23 11:02:48 +03:00
1b021ca5ca Merge remote-tracking branch 'origin/master' into dev
# Conflicts:
#	README.md
2021-10-23 11:02:31 +03:00
Atos1337
b1d9d8e8ba Add location property in virtual car 2021-10-20 16:59:30 +03:00
Atos1337
7c4d0a5f36 Change VirtualCar state and add controller 2021-10-20 16:50:18 +03:00
Atos1337
5322bf743b Implement VirtualCar 2021-10-20 12:53:48 +03:00
7ae75d8bd4 Add proper read after write for properties 2021-10-11 22:55:15 +03:00
Alexander Nozik
ded8790f39 Update README.md 2021-10-06 22:39:38 +03:00
b3898d2ab3 Add timestamp to all messages 2021-09-29 12:10:16 +03:00
553d819c54 Fix property names and types for DeviceSpec 2021-09-29 10:43:19 +03:00
3aa00ec491 Cleanup DeviceWithSpec API. Now all public things are in Spec 2021-08-04 14:14:05 +03:00
d503f0499e Add Eclipse Milo client 2021-08-03 21:05:36 +03:00
3606bc3a46 Migration to DF-0.5 and bug fixes 2021-08-02 16:42:19 +03:00
b068403429 Remove unnecessary active flag in ZMQ client 2021-07-19 09:41:41 +03:00
64d3f04469 Add additional application configuration to magix server 2021-07-06 14:02:51 +03:00
1cf7058778 add auto-reload to magix server status page 2021-07-05 17:31:46 +03:00
d0f22eec93 Use blocking access to solve ZMQ performance issues. 2021-07-04 14:44:53 +03:00
6e01e28015 ZMQ fully functional 2021-07-03 14:16:31 +03:00
89190db653 ZMQ support(untested) 2021-06-28 20:58:27 +03:00
596e3a0cfc Refactoring properties getters 2021-06-26 20:34:40 +03:00
fe3958fd08 Add alternative device syntax 2021-06-25 19:57:04 +03:00
e182af403f Add alternative device syntax 2021-06-24 10:41:42 +03:00
b1d3ba59bc Add alternative device syntax 2021-06-23 22:29:49 +03:00
a87b46cd2b Add alternative device syntax 2021-06-23 22:23:44 +03:00
28e6e24cf7 RSocket and demo device name refactor 2021-06-22 17:37:22 +03:00
3cbabd5d4b ZMQ client implementation 2021-06-21 15:37:36 +03:00
5e9cbab94d package refactor 2021-06-21 14:09:59 +03:00
d79d345a44 doocs payload 2021-06-20 21:11:13 +03:00
87440f688f Fix package names and imports 2021-06-20 17:35:50 +03:00
a510a8aedf Fix package names and imports 2021-06-20 17:05:07 +03:00
a093f1921e Implement Tango messages 2021-06-20 16:37:03 +03:00
6e4bf51a6f Move serializer to endpoint parameter 2021-06-20 14:46:02 +03:00
e3ea2304ef package rename 2021-04-11 15:12:09 +03:00
bf51768fe1 Upgrade versions 2021-04-05 17:41:36 +03:00
cb22374da5 Java rsocket bindings 2021-02-09 16:39:39 +03:00
b883bece48 message refactoring 2021-01-10 20:50:45 +03:00
d716eac07f fix build inconsistencies 2021-01-10 19:19:04 +03:00
2b4503c2fa Merge branch 'specialized-messages' into dev
# Conflicts:
#	build.gradle.kts
#	dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/HubController.kt
#	dataforge-device-server/src/main/kotlin/hep/dataforge/control/server/deviceWebServer.kt
#	magix/magix-service/src/commonMain/kotlin/hep/dataforge/magix/service/RSocketMagixEndpoint.kt
2021-01-10 18:45:19 +03:00
65acfe824b fix build inconsistencies 2021-01-10 18:41:52 +03:00
Alexander Nozik
319665e330 Update README.md 2021-01-07 20:13:10 +03:00
93c82db08e Message class hierarchy. 2020-12-15 14:01:03 +03:00
d68f5a9840 A lot of small refactoring in html 2020-12-12 10:44:41 +03:00
17beb29217 Message class hierarchy. 2020-12-02 12:35:16 +03:00
7fc27c49a3 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	build.gradle.kts
#	dataforge-magix-client/src/commonMain/kotlin/hep/dataforge/control/client/magixClient.kt
#	magix/magix-service/src/commonMain/kotlin/hep/dataforge/magix/service/RSocketMagixEndpoint.kt
2020-12-01 10:46:02 +03:00
92ab801967 Message format rework WIP 2020-12-01 10:33:17 +03:00
356f3e15a6 Remove CoroutineScope from Endpoint interface 2020-11-09 19:22:34 +03:00
dcf08d4426 Minor refactoring to adopt SharedFlow 2020-11-06 16:28:21 +03:00
78ee05371b Replace Scheme by data class in DeviceMessage 2020-11-05 11:29:40 +03:00
f3cfe9c6db Rsocket tcp client 2020-11-04 14:49:57 +03:00
5c90e8e07b Magix format converter 2020-11-03 22:33:13 +03:00
32c29240d2 Replace even id counter with uuid 2020-11-03 22:25:02 +03:00
f0acbbb8cc Add rsocket service 2020-11-03 18:54:52 +03:00
e5883dc318 Add rsocket server 2020-11-02 17:53:53 +03:00
8c8d53b187 Move to stand-alone sse 2020-10-21 23:16:15 +03:00
599d08b62a More or less working motors app 2020-10-12 15:37:46 +03:00
62dc6ef127 More or less working motors 2020-10-11 22:37:39 +03:00
23c66d9703 Fix motors parser 2020-10-10 22:38:43 +03:00
eb3121aed4 Refactor delegated properties 2020-10-10 17:21:28 +03:00
4b9f535002 refactoring 2020-10-08 11:20:08 +03:00
64043cf2c0 Fix motors debug server 2020-10-06 22:45:33 +03:00
dbf0466c64 Update and finalize message design 2020-10-06 15:33:15 +03:00
299 changed files with 19481 additions and 3441 deletions

24
.github/workflows/build.yml vendored Normal file
View 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

View File

@@ -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
View 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
View 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
View File

@@ -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
View File

@@ -0,0 +1,3 @@
[submodule "magix/rfc"]
path = magix/rfc
url = https://github.com/waltz-controls/rfc

45
.space.kts Normal file
View 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
View File

@@ -0,0 +1 @@
./space/* "Project Admin"

69
CHANGELOG.md Normal file
View File

@@ -0,0 +1,69 @@
# Changelog
## Unreleased
### Added
- Value averaging plot extension
- PLC4X bindings
### Changed
- Constructor properties return `DeviceStat` in order to be able to subscribe to them
- Refactored ports. Now we have `AsynchronousPort` as well as `SynchronousPort`
### Deprecated
### Removed
### Fixed
### Security
## 0.3.0 - 2024-03-04
### Added
- Device lifecycle message
- Low-code constructor
- Automatic description generation for spec properties (JVM only)
### Changed
- Property caching moved from core `Device` to the `CachingDevice`
- `DeviceSpec` properties no explicitly pass property name to getters and setters.
- `DeviceHub.respondHubMessage` now returns a list of messages to allow querying multiple devices. Device server also returns an array.
- DataForge 0.8.0
### Fixed
- Property writing does not trigger change if logical state already is the same as value to be set.
- Modbus-slave triggers only once for multi-register write.
- Removed unnecessary scope in hub messageFlow
## 0.2.2-dev-1 - 2023-09-24
### Changed
- updating logical state in `DeviceBase` is now protected and called `propertyChanged()`
- `DeviceBase` tries to read property after write if the writer does not set the value.
## 0.2.1 - 2023-09-24
### Added
- Core interfaces for building a device server
- Magix service for binding controls devices (both as RPC client and server)
- A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols
- A client and server connectors for OPC-UA via Eclipse Milo
- Implementation of byte ports on top os ktor-io asynchronous API
- Implementation of direct serial port communication with JSerialComm
- A combined Magix event loop server with web server for visualization.
- An API for stand-alone Controls-kt device or a hub.
- An implementation of controls-storage on top of JetBrains Xodus.
- A kotlin API for magix standard and some zero-dependency magix services
- Java API to work with magix endpoints without Kotlin
- MQTT client magix endpoint
- RabbitMQ client magix endpoint
- Magix endpoint (client) based on RSocket
- A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket routes.
- Magix history database API
- ZMQ client endpoint for Magix

229
README.md
View File

@@ -1,55 +1,228 @@
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
# DataForge-control
[![](https://maven.sciprog.center/api/badge/latest/kscience/space/kscience/controls-core-jvm?color=40c14a&name=repo.kotlin.link&prefix=v)](https://maven.sciprog.center/)
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
# Controls.kt
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
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:
- Describe devices and their properties.
- Describe devices and their properties.
- Collect data from devices and execute arbitrary actions supported by a device.
- 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-constructor](controls-constructor)
> A low-code constructor for composite devices simulation
>
> **Maturity**: PROTOTYPE
### [controls-core](controls-core)
> Core interfaces for building a device server
>
> **Maturity**: EXPERIMENTAL
>
> **Features:**
> - [device](controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt) : Device API with subscription (asynchronous and pseudo-synchronous properties)
> - [deviceMessage](controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt) : Specification for messages used to communicate between Controls-kt devices.
> - [deviceHub](controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt) : Grouping of devices into local tree-like hubs.
> - [deviceSpec](controls-core/src/commonMain/kotlin/space/kscience/controls/spec) : Mechanics and type-safe builders for devices. Including separation of device specification and device state.
> - [deviceManager](controls-core/src/commonMain/kotlin/space/kscience/controls/manager) : DataForge DI integration for devices. Includes device builders.
> - [ports](controls-core/src/commonMain/kotlin/space/kscience/controls/ports) : Working with asynchronous data sending and receiving raw byte arrays
### [controls-jupyter](controls-jupyter)
>
> **Maturity**: EXPERIMENTAL
### [controls-magix](controls-magix)
> Magix service for binding controls devices (both as RPC client and server)
>
> **Maturity**: EXPERIMENTAL
>
> **Features:**
> - [controlsMagix](controls-magix/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt) : Connect a `DeviceManage` with one or many devices to the Magix endpoint
> - [DeviceClient](controls-magix/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt) : A remote connector to Controls-kt device via Magix
### [controls-modbus](controls-modbus)
> A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols
>
> **Maturity**: EXPERIMENTAL
>
> **Features:**
> - [modbusRegistryMap](controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt) : Type-safe modbus registry map. Allows to define both single-register and multi-register entries (using DataForge IO).
Automatically checks consistency.
> - [modbusProcessImage](controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt) : Binding of slave (server) modbus device to Controls-kt device
> - [modbusDevice](controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt) : A device with additional methods to work with modbus registers.
### [controls-opcua](controls-opcua)
> A client and server connectors for OPC-UA via Eclipse Milo
>
> **Maturity**: EXPERIMENTAL
>
> **Features:**
> - [opcuaClient](controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client) : Connect a Controls-kt as a client to OPC UA server
> - [opcuaServer](controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server) : Create an OPC UA server on top of Controls-kt device (or device hub)
### [controls-pi](controls-pi)
> Utils to work with controls-kt on Raspberry pi
>
> **Maturity**: EXPERIMENTAL
### [controls-ports-ktor](controls-ports-ktor)
> Implementation of byte ports on top os ktor-io asynchronous API
>
> **Maturity**: PROTOTYPE
### [controls-serial](controls-serial)
> Implementation of direct serial port communication with JSerialComm
>
> **Maturity**: EXPERIMENTAL
### [controls-server](controls-server)
> A combined Magix event loop server with web server for visualization.
>
> **Maturity**: PROTOTYPE
### [controls-storage](controls-storage)
> An API for stand-alone Controls-kt device or a hub.
>
> **Maturity**: PROTOTYPE
### [controls-vision](controls-vision)
> Dashboard and visualization extensions for devices
>
> **Maturity**: PROTOTYPE
### [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/constructor](demo/constructor)
>
> **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
The demo includes a simple mock device with a few properties changing as `sin` and `cos` of
the current time. The device is configurable via a simple TornadoFX-based control panel.
You can run a demo by executing `application/run` Gradle task.
the current time. The device is configurable via a simple TornadoFX-based control panel.
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)

View File

@@ -1,30 +1,26 @@
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")
alias(libs.plugins.versions)
}
val dataforgeVersion: String by extra("0.2.0-dev-3")
val ktorVersion: String by extra("1.4.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.3.1-dev-1"
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()
}
repository("spc","https://maven.sciprog.center/kscience")
sonatype("https://oss.sonatype.org")
}
apiValidation {
validationDisabled = true
}
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")

View File

@@ -0,0 +1,21 @@
# Module controls-constructor
A low-code constructor for composite devices simulation
## Usage
## Artifact:
The Maven coordinates of this project are `space.kscience:controls-constructor:0.3.0`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:controls-constructor:0.3.0")
}
```

View File

@@ -0,0 +1,20 @@
plugins {
id("space.kscience.gradle.mpp")
`maven-publish`
}
description = """
A low-code constructor for composite devices simulation
""".trimIndent()
kscience{
jvm()
js()
dependencies {
api(projects.controlsCore)
}
}
readme{
maturity = space.kscience.gradle.Maturity.PROTOTYPE
}

View File

@@ -0,0 +1,118 @@
package space.kscience.controls.constructor
import space.kscience.controls.api.Device
import space.kscience.controls.api.PropertyDescriptor
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.time.Duration
/**
* A base for strongly typed device constructor blocks. Has additional delegates for type-safe devices
*/
public abstract class DeviceConstructor(
context: Context,
meta: Meta,
) : DeviceGroup(context, meta) {
/**
* Register a device, provided by a given [factory] and
*/
public fun <D : Device> device(
factory: Factory<D>,
meta: Meta? = null,
nameOverride: Name? = null,
metaLocation: Name? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
val name = nameOverride ?: property.name.asName()
val device = install(name, factory, meta, metaLocation ?: name)
ReadOnlyProperty { _: DeviceConstructor, _ ->
device
}
}
public fun <D : Device> device(
device: D,
nameOverride: Name? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, D>> =
PropertyDelegateProvider { _: DeviceConstructor, property: KProperty<*> ->
val name = nameOverride ?: property.name.asName()
install(name, device)
ReadOnlyProperty { _: DeviceConstructor, _ ->
device
}
}
/**
* Register a property and provide a direct reader for it
*/
public fun <T, S: DeviceState<T>> property(
state: S,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, S>> =
PropertyDelegateProvider { _: DeviceConstructor, property ->
val name = nameOverride ?: property.name
val descriptor = PropertyDescriptor(name).apply(descriptorBuilder)
registerProperty(descriptor, state)
ReadOnlyProperty { _: DeviceConstructor, _ ->
state
}
}
/**
* Register external state as a property
*/
public fun <T : Any> property(
metaConverter: MetaConverter<T>,
reader: suspend () -> T,
readInterval: Duration,
initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, DeviceState<T>>> = property(
DeviceState.external(this, metaConverter, readInterval, initialState, reader),
descriptorBuilder,
nameOverride,
)
/**
* Register a mutable external state as a property
*/
public fun <T : Any> mutableProperty(
metaConverter: MetaConverter<T>,
reader: suspend () -> T,
writer: suspend (T) -> Unit,
readInterval: Duration,
initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
DeviceState.external(this, metaConverter, readInterval, initialState, reader, writer),
descriptorBuilder,
nameOverride,
)
/**
* Create and register a virtual mutable property with optional [callback]
*/
public fun <T> virtualProperty(
metaConverter: MetaConverter<T>,
initialState: T,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
nameOverride: String? = null,
callback: (T) -> Unit = {},
): PropertyDelegateProvider<DeviceConstructor, ReadOnlyProperty<DeviceConstructor, MutableDeviceState<T>>> = property(
DeviceState.virtual(metaConverter, initialState, callback),
descriptorBuilder,
nameOverride,
)
}

View File

@@ -0,0 +1,298 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.*
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.controls.api.DeviceLifecycleState.*
import space.kscience.controls.manager.DeviceManager
import space.kscience.controls.manager.install
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.context.request
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import kotlin.collections.set
import kotlin.coroutines.CoroutineContext
/**
* A mutable group of devices and properties to be used for lightweight design and simulations.
*/
public open class DeviceGroup(
final override val context: Context,
override val meta: Meta,
) : DeviceHub, CachingDevice {
internal class Property(
val state: DeviceState<*>,
val descriptor: PropertyDescriptor,
)
internal class Action(
val invoke: suspend (Meta?) -> Meta?,
val descriptor: ActionDescriptor,
)
private val sharedMessageFlow = MutableSharedFlow<DeviceMessage>()
override val messageFlow: Flow<DeviceMessage>
get() = sharedMessageFlow
@OptIn(ExperimentalCoroutinesApi::class)
override val coroutineContext: CoroutineContext = context.newCoroutineContext(
SupervisorJob(context.coroutineContext[Job]) +
CoroutineName("Device $id") +
CoroutineExceptionHandler { _, throwable ->
context.launch {
sharedMessageFlow.emit(
DeviceErrorMessage(
errorMessage = throwable.message,
errorType = throwable::class.simpleName,
errorStackTrace = throwable.stackTraceToString()
)
)
}
}
)
private val _devices = hashMapOf<NameToken, Device>()
override val devices: Map<NameToken, Device> = _devices
/**
* Register and initialize (synchronize child's lifecycle state with group state) a new device in this group
*/
@OptIn(DFExperimental::class)
public fun <D : Device> install(token: NameToken, device: D): D {
require(_devices[token] == null) { "A child device with name $token already exists" }
//start the child device if needed
if (lifecycleState == STARTED || lifecycleState == STARTING) launch { device.start() }
_devices[token] = device
return device
}
private val properties: MutableMap<Name, Property> = hashMapOf()
/**
* Register a new property based on [DeviceState]. Properties could be modified dynamically
*/
public fun registerProperty(descriptor: PropertyDescriptor, state: DeviceState<*>) {
val name = descriptor.name.parseAsName()
require(properties[name] == null) { "Can't add property with name $name. It already exists." }
properties[name] = Property(state, descriptor)
state.metaFlow.onEach {
sharedMessageFlow.emit(
PropertyChangedMessage(
descriptor.name,
it
)
)
}.launchIn(this)
}
private val actions: MutableMap<Name, Action> = hashMapOf()
override val propertyDescriptors: Collection<PropertyDescriptor>
get() = properties.values.map { it.descriptor }
override val actionDescriptors: Collection<ActionDescriptor>
get() = actions.values.map { it.descriptor }
override suspend fun readProperty(propertyName: String): Meta =
properties[propertyName.parseAsName()]?.state?.valueAsMeta
?: error("Property with name $propertyName not found")
override fun getProperty(propertyName: String): Meta? = properties[propertyName.parseAsName()]?.state?.valueAsMeta
override suspend fun invalidate(propertyName: String) {
//does nothing for this implementation
}
override suspend fun writeProperty(propertyName: String, value: Meta) {
val property = (properties[propertyName.parseAsName()]?.state as? MutableDeviceState)
?: error("Property with name $propertyName not found")
property.valueAsMeta = value
}
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
val action = actions[actionName] ?: error("Action with name $actionName not found")
return action.invoke(argument)
}
@DFExperimental
override var lifecycleState: DeviceLifecycleState = STOPPED
protected set(value) {
if (field != value) {
launch {
sharedMessageFlow.emit(
DeviceLifeCycleMessage(value)
)
}
}
field = value
}
@OptIn(DFExperimental::class)
override suspend fun start() {
lifecycleState = STARTING
super.start()
devices.values.forEach {
it.start()
}
lifecycleState = STARTED
}
@OptIn(DFExperimental::class)
override fun stop() {
devices.values.forEach {
it.stop()
}
super.stop()
lifecycleState = STOPPED
}
public companion object {
}
}
public fun DeviceManager.registerDeviceGroup(
name: String = "@group",
meta: Meta = Meta.EMPTY,
block: DeviceGroup.() -> Unit,
): DeviceGroup {
val group = DeviceGroup(context, meta).apply(block)
install(name, group)
return group
}
public fun Context.registerDeviceGroup(
name: String = "@group",
meta: Meta = Meta.EMPTY,
block: DeviceGroup.() -> Unit,
): DeviceGroup = request(DeviceManager).registerDeviceGroup(name, meta, block)
private fun DeviceGroup.getOrCreateGroup(name: Name): DeviceGroup {
return when (name.length) {
0 -> this
1 -> {
val token = name.first()
when (val d = devices[token]) {
null -> install(
token,
DeviceGroup(context, meta[token] ?: Meta.EMPTY)
)
else -> (d as? DeviceGroup) ?: error("Device $name is not a DeviceGroup")
}
}
else -> getOrCreateGroup(name.first().asName()).getOrCreateGroup(name.cutFirst())
}
}
/**
* Register a device at given [name] path
*/
public fun <D : Device> DeviceGroup.install(name: Name, device: D): D {
return when (name.length) {
0 -> error("Can't use empty name for a child device")
1 -> install(name.first(), device)
else -> getOrCreateGroup(name.cutLast()).install(name.tokens.last(), device)
}
}
public fun <D : Device> DeviceGroup.install(name: String, device: D): D =
install(name.parseAsName(), device)
public fun <D : Device> DeviceGroup.install(device: D): D =
install(device.id, device)
public fun <D : Device> Context.install(name: String, device: D): D = request(DeviceManager).install(name, device)
/**
* Add a device creating intermediate groups if necessary. If device with given [name] already exists, throws an error.
* @param name the name of the device in the group
* @param factory a factory used to create a device
* @param deviceMeta meta override for this specific device
* @param metaLocation location of the template meta in parent group meta
*/
public fun <D : Device> DeviceGroup.install(
name: Name,
factory: Factory<D>,
deviceMeta: Meta? = null,
metaLocation: Name = name,
): D {
val newDevice = factory.build(context, Laminate(deviceMeta, meta[metaLocation]))
install(name, newDevice)
return newDevice
}
public fun <D : Device> DeviceGroup.install(
name: String,
factory: Factory<D>,
metaLocation: Name = name.parseAsName(),
metaBuilder: (MutableMeta.() -> Unit)? = null,
): D = install(name.parseAsName(), factory, metaBuilder?.let { Meta(it) }, metaLocation)
/**
* Create or edit a group with a given [name].
*/
public fun DeviceGroup.registerDeviceGroup(name: Name, block: DeviceGroup.() -> Unit): DeviceGroup =
getOrCreateGroup(name).apply(block)
public fun DeviceGroup.registerDeviceGroup(name: String, block: DeviceGroup.() -> Unit): DeviceGroup =
registerDeviceGroup(name.parseAsName(), block)
/**
* Register read-only property based on [state]
*/
public fun <T : Any> DeviceGroup.registerProperty(
name: String,
state: DeviceState<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
) {
registerProperty(
PropertyDescriptor(name).apply(descriptorBuilder),
state
)
}
/**
* Register a mutable property based on mutable [state]
*/
public fun <T : Any> DeviceGroup.registerMutableProperty(
name: String,
state: MutableDeviceState<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
) {
registerProperty(
PropertyDescriptor(name).apply(descriptorBuilder),
state
)
}
/**
* Create a new virtual mutable state and a property based on it.
* @return the mutable state used in property
*/
public fun <T : Any> DeviceGroup.registerVirtualProperty(
name: String,
initialValue: T,
converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
callback: (T) -> Unit = {},
): MutableDeviceState<T> {
val state = DeviceState.virtual<T>(converter, initialValue, callback)
registerMutableProperty(name, state, descriptorBuilder)
return state
}

View File

@@ -0,0 +1,242 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import space.kscience.controls.api.Device
import space.kscience.controls.api.PropertyChangedMessage
import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.MutableDevicePropertySpec
import space.kscience.controls.spec.name
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter
import kotlin.reflect.KProperty
import kotlin.time.Duration
/**
* An observable state of a device
*/
public interface DeviceState<T> {
public val converter: MetaConverter<T>
public val value: T
public val valueFlow: Flow<T>
public companion object
}
public val <T> DeviceState<T>.metaFlow: Flow<Meta> get() = valueFlow.map(converter::convert)
public val <T> DeviceState<T>.valueAsMeta: Meta get() = converter.convert(value)
public operator fun <T> DeviceState<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
/**
* Collect values in a given [scope]
*/
public fun <T> DeviceState<T>.collectValuesIn(scope: CoroutineScope, block: suspend (T)->Unit): Job =
valueFlow.onEach(block).launchIn(scope)
/**
* A mutable state of a device
*/
public interface MutableDeviceState<T> : DeviceState<T> {
override var value: T
}
public operator fun <T> MutableDeviceState<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
public var <T> MutableDeviceState<T>.valueAsMeta: Meta
get() = converter.convert(value)
set(arg) {
value = converter.read(arg)
}
/**
* A [MutableDeviceState] that does not correspond to a physical state
*
* @param callback a synchronous callback that could be used without a scope
*/
private class VirtualDeviceState<T>(
override val converter: MetaConverter<T>,
initialValue: T,
private val callback: (T) -> Unit = {},
) : MutableDeviceState<T> {
private val flow = MutableStateFlow(initialValue)
override val valueFlow: Flow<T> get() = flow
override var value: T
get() = flow.value
set(value) {
flow.value = value
callback(value)
}
}
/**
* A [MutableDeviceState] that does not correspond to a physical state
*
* @param callback a synchronous callback that could be used without a scope
*/
public fun <T> DeviceState.Companion.virtual(
converter: MetaConverter<T>,
initialValue: T,
callback: (T) -> Unit = {},
): MutableDeviceState<T> = VirtualDeviceState(converter, initialValue, callback)
private class StateFlowAsState<T>(
override val converter: MetaConverter<T>,
val flow: MutableStateFlow<T>,
) : MutableDeviceState<T> {
override var value: T by flow::value
override val valueFlow: Flow<T> get() = flow
}
public fun <T> MutableStateFlow<T>.asDeviceState(converter: MetaConverter<T>): DeviceState<T> =
StateFlowAsState(converter, this)
private open class BoundDeviceState<T>(
override val converter: MetaConverter<T>,
val device: Device,
val propertyName: String,
initialValue: T,
) : DeviceState<T> {
override val valueFlow: StateFlow<T> = device.messageFlow.filterIsInstance<PropertyChangedMessage>().filter {
it.property == propertyName
}.mapNotNull {
converter.read(it.value)
}.stateIn(device.context, SharingStarted.Eagerly, initialValue)
override val value: T get() = valueFlow.value
}
/**
* Bind a read-only [DeviceState] to a [Device] property
*/
public suspend fun <T> Device.propertyAsState(
propertyName: String,
metaConverter: MetaConverter<T>,
): DeviceState<T> {
val initialValue = metaConverter.readOrNull(readProperty(propertyName)) ?: error("Conversion of property failed")
return BoundDeviceState(metaConverter, this, propertyName, initialValue)
}
public suspend fun <D : Device, T> D.propertyAsState(
propertySpec: DevicePropertySpec<D, T>,
): DeviceState<T> = propertyAsState(propertySpec.name, propertySpec.converter)
public fun <T, R> DeviceState<T>.map(
converter: MetaConverter<R>, mapper: (T) -> R,
): DeviceState<R> = object : DeviceState<R> {
override val converter: MetaConverter<R> = converter
override val value: R
get() = mapper(this@map.value)
override val valueFlow: Flow<R> = this@map.valueFlow.map(mapper)
}
private class MutableBoundDeviceState<T>(
converter: MetaConverter<T>,
device: Device,
propertyName: String,
initialValue: T,
) : BoundDeviceState<T>(converter, device, propertyName, initialValue), MutableDeviceState<T> {
override var value: T
get() = valueFlow.value
set(newValue) {
device.launch {
device.writeProperty(propertyName, converter.convert(newValue))
}
}
}
public fun <T> Device.mutablePropertyAsState(
propertyName: String,
metaConverter: MetaConverter<T>,
initialValue: T,
): MutableDeviceState<T> = MutableBoundDeviceState(metaConverter, this, propertyName, initialValue)
public suspend fun <T> Device.mutablePropertyAsState(
propertyName: String,
metaConverter: MetaConverter<T>,
): MutableDeviceState<T> {
val initialValue = metaConverter.readOrNull(readProperty(propertyName)) ?: error("Conversion of property failed")
return mutablePropertyAsState(propertyName, metaConverter, initialValue)
}
public suspend fun <D : Device, T> D.mutablePropertyAsState(
propertySpec: MutableDevicePropertySpec<D, T>,
): MutableDeviceState<T> = mutablePropertyAsState(propertySpec.name, propertySpec.converter)
public fun <D : Device, T> D.mutablePropertyAsState(
propertySpec: MutableDevicePropertySpec<D, T>,
initialValue: T,
): MutableDeviceState<T> = mutablePropertyAsState(propertySpec.name, propertySpec.converter, initialValue)
private open class ExternalState<T>(
val scope: CoroutineScope,
override val converter: MetaConverter<T>,
val readInterval: Duration,
initialValue: T,
val reader: suspend () -> T,
) : DeviceState<T> {
protected val flow: StateFlow<T> = flow {
while (true) {
delay(readInterval)
emit(reader())
}
}.stateIn(scope, SharingStarted.Eagerly, initialValue)
override val value: T get() = flow.value
override val valueFlow: Flow<T> get() = flow
}
/**
* Create a [DeviceState] which is constructed by periodically reading external value
*/
public fun <T> DeviceState.Companion.external(
scope: CoroutineScope,
converter: MetaConverter<T>,
readInterval: Duration,
initialValue: T,
reader: suspend () -> T,
): DeviceState<T> = ExternalState(scope, converter, readInterval, initialValue, reader)
private class MutableExternalState<T>(
scope: CoroutineScope,
converter: MetaConverter<T>,
readInterval: Duration,
initialValue: T,
reader: suspend () -> T,
val writer: suspend (T) -> Unit,
) : ExternalState<T>(scope, converter, readInterval, initialValue, reader), MutableDeviceState<T> {
override var value: T
get() = super.value
set(value) {
scope.launch {
writer(value)
}
}
}
/**
* Create a [DeviceState] that regularly reads and caches an external value
*/
public fun <T> DeviceState.Companion.external(
scope: CoroutineScope,
converter: MetaConverter<T>,
readInterval: Duration,
initialValue: T,
reader: suspend () -> T,
writer: suspend (T) -> Unit,
): MutableDeviceState<T> = MutableExternalState(scope, converter, readInterval, initialValue, reader, writer)

View File

@@ -0,0 +1,99 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.controls.api.Device
import space.kscience.controls.manager.clock
import space.kscience.controls.spec.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.meta.MetaConverter
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import kotlin.math.pow
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
/**
* A classic drive regulated by force with encoder
*/
public interface Drive : Device {
/**
* Get or set drive force or momentum
*/
public var force: Double
/**
* Current position value
*/
public val position: Double
public companion object : DeviceSpec<Drive>() {
public val force: MutableDevicePropertySpec<Drive, Double> by Drive.mutableProperty(
MetaConverter.double,
Drive::force
)
public val position: DevicePropertySpec<Drive, Double> by doubleProperty { position }
}
}
/**
* A virtual drive
*/
public class VirtualDrive(
context: Context,
private val mass: Double,
public val positionState: MutableDeviceState<Double>,
) : Drive, DeviceBySpec<Drive>(Drive, context) {
private val dt = meta["time.step"].double?.milliseconds ?: 1.milliseconds
private val clock = context.clock
override var force: Double = 0.0
override val position: Double get() = positionState.value
public var velocity: Double = 0.0
private set
private var updateJob: Job? = null
override suspend fun onStart() {
updateJob = launch {
var lastTime = clock.now()
while (isActive) {
delay(dt)
val realTime = clock.now()
val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS)
//set last time and value to new values
lastTime = realTime
// compute new value based on velocity and acceleration from the previous step
positionState.value += velocity * dtSeconds + force / mass * dtSeconds.pow(2) / 2
propertyChanged(Drive.position, positionState.value)
// compute new velocity based on acceleration on the previous step
velocity += force / mass * dtSeconds
}
}
}
override fun onStop() {
updateJob?.cancel()
}
public companion object {
public fun factory(
mass: Double,
positionState: MutableDeviceState<Double>,
): Factory<Drive> = Factory { context, _ ->
VirtualDrive(context, mass, positionState)
}
}
}
public suspend fun Drive.stateOfForce(): MutableDeviceState<Double> = mutablePropertyAsState(Drive.force)

View File

@@ -0,0 +1,44 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import space.kscience.controls.api.Device
import space.kscience.controls.spec.DeviceBySpec
import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.DeviceSpec
import space.kscience.controls.spec.booleanProperty
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory
/**
* A limit switch device
*/
public interface LimitSwitch : Device {
public val locked: Boolean
public companion object : DeviceSpec<LimitSwitch>() {
public val locked: DevicePropertySpec<LimitSwitch, Boolean> by booleanProperty { locked }
public fun factory(lockedState: DeviceState<Boolean>): Factory<LimitSwitch> = Factory { context, _ ->
VirtualLimitSwitch(context, lockedState)
}
}
}
/**
* Virtual [LimitSwitch]
*/
public class VirtualLimitSwitch(
context: Context,
public val lockedState: DeviceState<Boolean>,
) : DeviceBySpec<LimitSwitch>(LimitSwitch, context), LimitSwitch {
init {
lockedState.valueFlow.onEach {
propertyChanged(LimitSwitch.locked, it)
}.launchIn(this)
}
override val locked: Boolean get() = lockedState.value
}

View File

@@ -0,0 +1,92 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Instant
import space.kscience.controls.manager.clock
import space.kscience.controls.spec.DeviceBySpec
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
/**
* Pid regulator parameters
*/
public interface PidParameters {
public val kp: Double
public val ki: Double
public val kd: Double
public val timeStep: Duration
}
private data class PidParametersImpl(
override val kp: Double,
override val ki: Double,
override val kd: Double,
override val timeStep: Duration,
) : PidParameters
public fun PidParameters(kp: Double, ki: Double, kd: Double, timeStep: Duration = 1.milliseconds): PidParameters =
PidParametersImpl(kp, ki, kd, timeStep)
/**
* A drive with PID regulator
*/
public class PidRegulator(
public val drive: Drive,
public val pidParameters: PidParameters,
) : DeviceBySpec<Regulator>(Regulator, drive.context), Regulator {
private val clock = drive.context.clock
override var target: Double = drive.position
private var lastTime: Instant = clock.now()
private var lastPosition: Double = target
private var integral: Double = 0.0
private var updateJob: Job? = null
private val mutex = Mutex()
override suspend fun onStart() {
drive.start()
updateJob = launch {
while (isActive) {
delay(pidParameters.timeStep)
mutex.withLock {
val realTime = clock.now()
val delta = target - position
val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS)
integral += delta * dtSeconds
val derivative = (drive.position - lastPosition) / dtSeconds
//set last time and value to new values
lastTime = realTime
lastPosition = drive.position
drive.force = pidParameters.kp * delta + pidParameters.ki * integral + pidParameters.kd * derivative
propertyChanged(Regulator.position, drive.position)
}
}
}
}
override fun onStop() {
updateJob?.cancel()
}
override val position: Double get() = drive.position
}
public fun DeviceGroup.pid(
name: String,
drive: Drive,
pidParameters: PidParameters,
): PidRegulator = install(name, PidRegulator(drive, pidParameters))

View File

@@ -0,0 +1,27 @@
package space.kscience.controls.constructor
import space.kscience.controls.api.Device
import space.kscience.controls.spec.*
import space.kscience.dataforge.meta.MetaConverter
/**
* A regulator with target value and current position
*/
public interface Regulator : Device {
/**
* Get or set target value
*/
public var target: Double
/**
* Current position value
*/
public val position: Double
public companion object : DeviceSpec<Regulator>() {
public val target: MutableDevicePropertySpec<Regulator, Double> by mutableProperty(MetaConverter.double, Regulator::target)
public val position: DevicePropertySpec<Regulator, Double> by doubleProperty { position }
}
}

View File

@@ -0,0 +1,47 @@
package space.kscience.controls.constructor
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import space.kscience.dataforge.meta.MetaConverter
/**
* A state describing a [Double] value in the [range]
*/
public class DoubleRangeState(
initialValue: Double,
public val range: ClosedFloatingPointRange<Double>,
) : MutableDeviceState<Double> {
init {
require(initialValue in range) { "Initial value should be in range" }
}
override val converter: MetaConverter<Double> = MetaConverter.double
private val _valueFlow = MutableStateFlow(initialValue)
override var value: Double
get() = _valueFlow.value
set(newValue) {
_valueFlow.value = newValue.coerceIn(range)
}
override val valueFlow: StateFlow<Double> get() = _valueFlow
/**
* A state showing that the range is on its lower boundary
*/
public val atStartState: DeviceState<Boolean> = map(MetaConverter.boolean) { it <= range.start }
/**
* A state showing that the range is on its higher boundary
*/
public val atEndState: DeviceState<Boolean> = map(MetaConverter.boolean) { it >= range.endInclusive }
}
@Suppress("UnusedReceiverParameter")
public fun DeviceGroup.rangeState(
initialValue: Double,
range: ClosedFloatingPointRange<Double>,
): DoubleRangeState = DoubleRangeState(initialValue, range)

31
controls-core/README.md Normal file
View File

@@ -0,0 +1,31 @@
# 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.3.0`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:controls-core:0.3.0")
}
```

View 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;
}

View File

@@ -0,0 +1,70 @@
import space.kscience.gradle.Maturity
plugins {
id("space.kscience.gradle.mpp")
`maven-publish`
}
description = """
Core interfaces for building a device server
""".trimIndent()
kscience {
jvm()
js()
native()
useCoroutines()
useSerialization{
json()
}
useContextReceivers()
commonMain {
api(libs.dataforge.io)
api(spclibs.kotlinx.datetime)
}
jvmTest{
implementation(spclibs.logback.classic)
}
}
readme{
maturity = Maturity.EXPERIMENTAL
feature("device", ref = "src/commonMain/kotlin/space/kscience/controls/api/Device.kt"){
"""
Device API with subscription (asynchronous and pseudo-synchronous properties)
""".trimIndent()
}
feature("deviceMessage", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt"){
"""
Specification for messages used to communicate between Controls-kt devices.
""".trimIndent()
}
feature("deviceHub", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt"){
"""
Grouping of devices into local tree-like hubs.
""".trimIndent()
}
feature("deviceSpec", ref = "src/commonMain/kotlin/space/kscience/controls/spec"){
"""
Mechanics and type-safe builders for devices. Including separation of device specification and device state.
""".trimIndent()
}
feature("deviceManager", ref = "src/commonMain/kotlin/space/kscience/controls/manager"){
"""
DataForge DI integration for devices. Includes device builders.
""".trimIndent()
}
feature("ports", ref = "src/commonMain/kotlin/space/kscience/controls/ports"){
"""
Working with asynchronous data sending and receiving raw byte arrays
""".trimIndent()
}
}

View File

@@ -0,0 +1,40 @@
package space.kscience.controls.api
import kotlinx.coroutines.flow.Flow
/**
* A generic bidirectional asynchronous sender/receiver object
*/
public interface AsynchronousSocket<T> : AutoCloseable {
/**
* Send an object to the socket
*/
public suspend fun send(data: T)
/**
* Flow of objects received from socket
*/
public fun subscribe(): Flow<T>
/**
* Start socket operation
*/
public fun open()
/**
* Check if this socket is open
*/
public val isOpen: Boolean
}
/**
* Connect an input to this socket.
* Multiple inputs could be connected to the same [AsynchronousSocket].
*
* This method suspends indefinitely, so it should be started in a separate coroutine.
*/
public suspend fun <T> AsynchronousSocket<T>.sendFlow(flow: Flow<T>) {
flow.collect { send(it) }
}

View File

@@ -0,0 +1,171 @@
package space.kscience.controls.api
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.*
import kotlinx.serialization.Serializable
import space.kscience.controls.api.Device.Companion.DEVICE_TARGET
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DfType
import space.kscience.dataforge.names.parseAsName
/**
* A lifecycle state of a device
*/
@Serializable
public enum class DeviceLifecycleState {
/**
* Device is initializing
*/
STARTING,
/**
* The Device is initialized and running
*/
STARTED,
/**
* The Device is closed
*/
STOPPED,
/**
* The device encountered irrecoverable error
*/
ERROR
}
/**
* General interface describing a managed Device.
* [Device] is a supervisor scope encompassing all operations on a device.
* When canceled, cancels all running processes.
*/
@DfType(DEVICE_TARGET)
public interface Device : ContextAware, CoroutineScope {
/**
* Initial configuration meta for the device
*/
public val meta: Meta get() = Meta.EMPTY
/**
* List of supported property descriptors
*/
public val propertyDescriptors: Collection<PropertyDescriptor>
/**
* List of supported action descriptors. Action is a request to the device that
* may or may not change the properties
*/
public val actionDescriptors: Collection<ActionDescriptor>
/**
* Read the physical state of property and update/push notifications if needed.
*/
public suspend fun readProperty(propertyName: String): Meta
/**
* Set property [value] for a property with name [propertyName].
* In rare cases could suspend if the [Device] supports command queue, and it is full at the moment.
*/
public suspend fun writeProperty(propertyName: String, value: Meta)
/**
* A subscription-based [Flow] of [DeviceMessage] provided by device. The flow is guaranteed to be readable
* multiple times.
*/
public val messageFlow: Flow<DeviceMessage>
/**
* Send an action request and suspend caller while request is being processed.
* Could return null if request does not return a meaningful answer.
*/
public suspend fun execute(actionName: String, argument: Meta? = null): Meta?
/**
* Initialize the device. This function suspends until the device is finished initialization.
* Does nothing if the device is started or is starting
*/
public suspend fun start(): Unit = Unit
/**
* Close and terminate the device. This function does not wait for the device to be closed.
*/
public fun stop() {
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"
}
}
/**
* Inner id of a device. Not necessary corresponds to the name in the parent container
*/
public val Device.id: String get() = meta["id"].string?: "device[${hashCode().toString(16)}]"
/**
* Device that caches properties values
*/
public interface CachingDevice : Device {
/**
* Immediately (without waiting) get the cached (logical) state of property or return null if it is invalid
*/
public fun getProperty(propertyName: String): Meta?
/**
* Invalidate property (set logical state to invalid).
*
* This message is suspended to provide lock-free local property changes (they require coroutine context).
*/
public suspend fun invalidate(propertyName: String)
}
/**
* Get the logical state of property or suspend to read the physical value.
*/
public suspend fun Device.getOrReadProperty(propertyName: String): Meta = if (this is CachingDevice) {
getProperty(propertyName) ?: readProperty(propertyName)
} else {
readProperty(propertyName)
}
/**
* Get a snapshot of the device logical state
*
*/
public fun CachingDevice.getAllProperties(): Meta = Meta {
for (descriptor in propertyDescriptors) {
set(descriptor.name.parseAsName(), getProperty(descriptor.name))
}
}
/**
* Subscribe on property changes for the whole device
*/
public fun Device.onPropertyChange(
scope: CoroutineScope = this,
callback: suspend PropertyChangedMessage.() -> Unit,
): Job = messageFlow.filterIsInstance<PropertyChangedMessage>().onEach(callback).launchIn(scope)
/**
* A [Flow] of property change messages for specific property.
*/
public fun Device.propertyMessageFlow(propertyName: String): Flow<PropertyChangedMessage> = messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == propertyName }

View File

@@ -0,0 +1,79 @@
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
/**
* List all devices, including sub-devices
*/
public fun buildDeviceTree(): Map<Name, Device> = buildMap {
fun putAll(prefix: Name, hub: DeviceHub) {
hub.devices.forEach {
put(prefix + it.key, it.value)
}
}
devices.forEach {
val name = it.key.asName()
put(name, it.value)
(it.value as? DeviceHub)?.let { hub ->
putAll(name, hub)
}
}
}
override fun content(target: String): Map<Name, Any> = if (target == Device.DEVICE_TARGET) {
buildDeviceTree()
} 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)
//}

View File

@@ -0,0 +1,249 @@
@file:OptIn(ExperimentalSerializationApi::class)
package space.kscience.controls.api
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import space.kscience.dataforge.io.Envelope
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.toJson
import space.kscience.dataforge.meta.toMeta
import space.kscience.dataforge.names.Name
@Serializable
public sealed class DeviceMessage {
public abstract val sourceDevice: Name?
public abstract val targetDevice: Name?
public abstract val comment: String?
public abstract val time: Instant
/**
* Update the source device name for composition. If the original name is null, the resulting name is also null.
*/
public abstract fun changeSource(block: (Name) -> Name): DeviceMessage
public companion object {
public fun error(
cause: Throwable,
sourceDevice: Name,
targetDevice: Name? = null,
): DeviceErrorMessage = DeviceErrorMessage(
errorMessage = cause.message,
errorType = cause::class.simpleName,
errorStackTrace = cause.stackTraceToString(),
sourceDevice = sourceDevice,
targetDevice = targetDevice
)
public fun fromMeta(meta: Meta): DeviceMessage = Json.decodeFromJsonElement(meta.toJson())
}
}
/**
* Notify that property is changed. [sourceDevice] is mandatory.
* [property] corresponds to property name.
*
*/
@Serializable
@SerialName("property.changed")
public data class PropertyChangedMessage(
public val property: String,
public val value: Meta,
override val sourceDevice: Name = Name.EMPTY,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
}
/**
* A command to set or invalidate property. [targetDevice] is mandatory.
*/
@Serializable
@SerialName("property.set")
public data class PropertySetMessage(
public val property: String,
public val value: Meta,
override val sourceDevice: Name? = null,
override val targetDevice: Name,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
}
/**
* A command to request property value asynchronously. [targetDevice] is mandatory.
* The property value should be returned asynchronously via [PropertyChangedMessage].
*/
@Serializable
@SerialName("property.get")
public data class PropertyGetMessage(
public val property: String,
override val sourceDevice: Name? = null,
override val targetDevice: Name,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
}
/**
* Request device description. The result is returned in form of [DescriptionMessage]
*/
@Serializable
@SerialName("description.get")
public data class GetDescriptionMessage(
override val sourceDevice: Name? = null,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
}
/**
* The full device description message
*/
@Serializable
@SerialName("description")
public data class DescriptionMessage(
val description: Meta,
val properties: Collection<PropertyDescriptor>,
val actions: Collection<ActionDescriptor>,
override val sourceDevice: Name,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
}
/**
* A request to execute an action. [targetDevice] is mandatory
*
* @param requestId action request id that should be returned in a response
*/
@Serializable
@SerialName("action.execute")
public data class ActionExecuteMessage(
public val action: String,
public val argument: Meta?,
public val requestId: String,
override val sourceDevice: Name? = null,
override val targetDevice: Name,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
}
/**
* Asynchronous action result. [sourceDevice] is mandatory
*
* @param requestId request id passed in the request
*/
@Serializable
@SerialName("action.result")
public data class ActionResultMessage(
public val action: String,
public val result: Meta?,
public val requestId: String,
override val sourceDevice: Name,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
}
/**
* Notifies listeners that a new binary with given [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 = Name.EMPTY,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
}
/**
* The evaluation of the message produced a service error
*/
@Serializable
@SerialName("error")
public data class DeviceErrorMessage(
public val errorMessage: String?,
public val errorType: String? = null,
public val errorStackTrace: String? = null,
override val sourceDevice: Name = Name.EMPTY,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
}
/**
* Device [Device.lifecycleState] is changed
*/
@Serializable
@SerialName("lifecycle")
public data class DeviceLifeCycleMessage(
val state: DeviceLifecycleState,
override val sourceDevice: Name = Name.EMPTY,
override val targetDevice: Name? = null,
override val comment: String? = null,
@EncodeDefault override val time: Instant = Clock.System.now(),
) : DeviceMessage() {
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
}
public fun DeviceMessage.toMeta(): Meta = Json.encodeToJsonElement(this).toMeta()
public fun DeviceMessage.toEnvelope(): Envelope = Envelope(toMeta(), null)

View File

@@ -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 description: String? = null,
public var metaDescriptor: MetaDescriptor = MetaDescriptor(),
public var readable: Boolean = true,
public var mutable: Boolean = false
)
public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.()->Unit){
metaDescriptor = MetaDescriptor(block)
}
/**
* A descriptor for property
*/
@Serializable
public class ActionDescriptor(public val name: String) {
public var description: String? = null
}

View File

@@ -0,0 +1,25 @@
package space.kscience.controls.manager
import kotlinx.datetime.Clock
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 ClockManager : AbstractPlugin() {
override val tag: PluginTag get() = DeviceManager.tag
public val clock: Clock by lazy {
//TODO add clock customization
Clock.System
}
public companion object : PluginFactory<ClockManager> {
override val tag: PluginTag = PluginTag("clock", group = PluginTag.DATAFORGE_GROUP)
override fun build(context: Context, meta: Meta): ClockManager = ClockManager()
}
}
public val Context.clock: Clock get() = plugins[ClockManager]?.clock ?: Clock.System

View File

@@ -0,0 +1,79 @@
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.controls.api.id
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.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.start()
}
return device
}
public fun <D : Device> DeviceManager.install(device: D): D = install(device.id, device)
/**
* Register and start a device built by [factory] with current [Context] and [meta].
*/
public fun <D : Device> DeviceManager.install(name: String, factory: Factory<D>, meta: Meta = Meta.EMPTY): D =
install(name, factory(meta, context))
/**
* A delegate that initializes device on the first use
*/
public inline fun <D : Device> DeviceManager.installing(
factory: Factory<D>,
builder: MutableMeta.() -> Unit = {},
): ReadOnlyProperty<Any?, D> {
val meta = Meta(builder)
return ReadOnlyProperty { _, property ->
val name = property.name
val current = 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
}
}
}

View File

@@ -0,0 +1,107 @@
package space.kscience.controls.manager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import space.kscience.controls.api.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
/**
* Process a message targeted at this [Device], assuming its name is [deviceTarget].
*/
public suspend fun Device.respondMessage(deviceTarget: Name, request: DeviceMessage): DeviceMessage? = try {
when (request) {
is PropertyGetMessage -> {
PropertyChangedMessage(
property = request.property,
value = getOrReadProperty(request.property),
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice
)
}
is PropertySetMessage -> {
writeProperty(request.property, request.value)
PropertyChangedMessage(
property = request.property,
value = getOrReadProperty(request.property),
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice
)
}
is ActionExecuteMessage -> {
ActionResultMessage(
action = request.action,
result = execute(request.action, request.argument),
requestId = request.requestId,
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice
)
}
is GetDescriptionMessage -> {
DescriptionMessage(
description = meta,
properties = propertyDescriptors,
actions = actionDescriptors,
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice
)
}
is DescriptionMessage,
is PropertyChangedMessage,
is ActionResultMessage,
is BinaryNotificationMessage,
is DeviceErrorMessage,
is EmptyDeviceMessage,
is DeviceLogMessage,
is DeviceLifeCycleMessage,
-> null
}
} catch (ex: Exception) {
DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice)
}
/**
* Process incoming [DeviceMessage], using hub naming to find target.
* If the `targetDevice` is `null`, then message is sent to each device in this hub
*/
public suspend fun DeviceHub.respondHubMessage(request: DeviceMessage): List<DeviceMessage> {
return try {
val targetName = request.targetDevice
if (targetName == null) {
buildDeviceTree().mapNotNull {
it.value.respondMessage(it.key, request)
}
} else {
val device = getOrNull(targetName) ?: error("The device with name $targetName not found in $this")
listOfNotNull(device.respondMessage(targetName, request))
}
} catch (ex: Exception) {
listOf(DeviceMessage.error(ex, sourceDevice = Name.EMPTY, targetDevice = request.sourceDevice))
}
}
/**
* Collect all messages from given [DeviceHub], applying proper relative names.
*/
public fun DeviceHub.hubMessageFlow(): Flow<DeviceMessage> {
val deviceMessageFlow = if (this is Device) messageFlow else emptyFlow()
val childrenFlows = devices.map { (token, childDevice) ->
if (childDevice is DeviceHub) {
childDevice.hubMessageFlow()
} else {
childDevice.messageFlow
}.map { deviceMessage ->
deviceMessage.changeSource { token + it }
}
}
return merge(deviceMessageFlow, *childrenFlows.toTypedArray())
}

View File

@@ -0,0 +1,70 @@
package space.kscience.controls.misc
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import space.kscience.controls.api.Device
import space.kscience.controls.api.DeviceMessage
import space.kscience.controls.api.PropertyChangedMessage
import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.name
import space.kscience.dataforge.meta.MetaConverter
import space.kscience.dataforge.names.Name
/**
* An interface for device property history.
*/
public interface PropertyHistory<T> {
/**
* Flow property values filtered by a time range. The implementation could flow it as a chunk or provide paging.
* So the resulting flow is allowed to suspend.
*
* If [until] is in the future, the resulting flow is potentially unlimited.
* Theoretically, it could be also unlimited if the event source keeps producing new event with timestamp in a given range.
*/
public fun flowHistory(
from: Instant = Instant.DISTANT_PAST,
until: Instant = Clock.System.now(),
): Flow<ValueWithTime<T>>
}
/**
* An in-memory property values history collector
*/
public class CollectedPropertyHistory<T>(
public val scope: CoroutineScope,
eventFlow: Flow<DeviceMessage>,
public val deviceName: Name,
public val propertyName: String,
public val converter: MetaConverter<T>,
maxSize: Int = 1000,
) : PropertyHistory<T> {
private val store: SharedFlow<ValueWithTime<T>> = eventFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.sourceDevice == deviceName && it.property == propertyName }
.map { ValueWithTime(converter.read(it.value), it.time) }
.shareIn(scope, started = SharingStarted.Eagerly, replay = maxSize)
override fun flowHistory(from: Instant, until: Instant): Flow<ValueWithTime<T>> =
store.filter { it.time in from..until }
}
/**
* Collect and store in memory device property changes for a given property
*/
public fun <T> Device.collectPropertyHistory(
scope: CoroutineScope = this,
deviceName: Name,
propertyName: String,
converter: MetaConverter<T>,
maxSize: Int = 1000,
): PropertyHistory<T> = CollectedPropertyHistory(scope, messageFlow, deviceName, propertyName, converter, maxSize)
public fun <D : Device, T> D.collectPropertyHistory(
scope: CoroutineScope = this,
deviceName: Name,
spec: DevicePropertySpec<D, T>,
maxSize: Int = 1000,
): PropertyHistory<T> = collectPropertyHistory(scope, deviceName, spec.name, spec.converter, maxSize)

View File

@@ -0,0 +1,69 @@
package space.kscience.controls.misc
import kotlinx.datetime.Instant
import kotlinx.io.Sink
import kotlinx.io.Source
import space.kscience.dataforge.io.IOFormat
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter
import space.kscience.dataforge.meta.get
/**
* A value coupled to a time it was obtained at
*/
public data class ValueWithTime<T>(val value: T, val time: Instant) {
public companion object {
/**
* Create a [ValueWithTime] format for given value value [IOFormat]
*/
public fun <T> ioFormat(
valueFormat: IOFormat<T>,
): IOFormat<ValueWithTime<T>> = ValueWithTimeIOFormat(valueFormat)
/**
* Create a [MetaConverter] with time for given value [MetaConverter]
*/
public fun <T> metaConverter(
valueConverter: MetaConverter<T>,
): MetaConverter<ValueWithTime<T>> = ValueWithTimeMetaConverter(valueConverter)
public const val META_TIME_KEY: String = "time"
public const val META_VALUE_KEY: String = "value"
}
}
private class ValueWithTimeIOFormat<T>(val valueFormat: IOFormat<T>) : IOFormat<ValueWithTime<T>> {
override fun readFrom(source: Source): ValueWithTime<T> {
val timestamp = InstantIOFormat.readFrom(source)
val value = valueFormat.readFrom(source)
return ValueWithTime(value, timestamp)
}
override fun writeTo(sink: Sink, obj: ValueWithTime<T>) {
InstantIOFormat.writeTo(sink, obj.time)
valueFormat.writeTo(sink, obj.value)
}
}
private class ValueWithTimeMetaConverter<T>(
val valueConverter: MetaConverter<T>,
) : MetaConverter<ValueWithTime<T>> {
override fun readOrNull(
source: Meta,
): ValueWithTime<T>? = valueConverter.read(source[ValueWithTime.META_VALUE_KEY] ?: Meta.EMPTY)?.let {
ValueWithTime(it, source[ValueWithTime.META_TIME_KEY]?.instant ?: Instant.DISTANT_PAST)
}
override fun convert(obj: ValueWithTime<T>): Meta = Meta {
ValueWithTime.META_TIME_KEY put obj.time.toMeta()
ValueWithTime.META_VALUE_KEY put valueConverter.convert(obj.value)
}
}
public fun <T : Any> MetaConverter<T>.withTime(): MetaConverter<ValueWithTime<T>> = ValueWithTimeMetaConverter(this)

View File

@@ -0,0 +1,42 @@
package space.kscience.controls.misc
import kotlinx.datetime.Instant
import kotlinx.io.Sink
import kotlinx.io.Source
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.IOFormat
import space.kscience.dataforge.io.IOFormatFactory
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* An [IOFormat] for [Instant]
*/
public object InstantIOFormat : IOFormat<Instant>, IOFormatFactory<Instant> {
override fun build(context: Context, meta: Meta): IOFormat<Instant> = this
override val name: Name = "instant".asName()
override val type: KType get() = typeOf<Instant>()
override fun writeTo(sink: Sink, obj: Instant) {
sink.writeLong(obj.epochSeconds)
sink.writeInt(obj.nanosecondsOfSecond)
}
override fun readFrom(source: Source): Instant {
val seconds = source.readLong()
val nanoseconds = source.readInt()
return Instant.fromEpochSeconds(seconds, nanoseconds)
}
}
public fun Instant.toMeta(): Meta = Meta(toString())
public val Meta.instant: Instant? get() = value?.string?.let { Instant.parse(it) }

View File

@@ -0,0 +1,125 @@
package space.kscience.controls.ports
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.io.Buffer
import kotlinx.io.Source
import space.kscience.controls.api.AsynchronousSocket
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string
import kotlin.coroutines.CoroutineContext
/**
* Raw [ByteArray] port
*/
public interface AsynchronousPort : ContextAware, AsynchronousSocket<ByteArray>
/**
* Capture [AsynchronousPort] output as kotlinx-io [Source].
* [scope] controls the consummation.
* If the scope is canceled, the source stops producing.
*/
public fun AsynchronousPort.receiveAsSource(scope: CoroutineScope): Source {
val buffer = Buffer()
subscribe().onEach {
buffer.write(it)
}.launchIn(scope)
return buffer
}
/**
* Common abstraction for [AsynchronousPort] based on [Channel]
*/
public abstract class AbstractAsynchronousPort(
override val context: Context,
public val meta: Meta,
coroutineContext: CoroutineContext = context.coroutineContext,
) : AsynchronousPort {
protected val scope: CoroutineScope by lazy {
CoroutineScope(
coroutineContext +
SupervisorJob(coroutineContext[Job]) +
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) { throwable.stackTraceToString() } } +
CoroutineName(toString())
)
}
private val outgoing = Channel<ByteArray>(meta["outgoing.capacity"].int?:100)
private val incoming = Channel<ByteArray>(meta["incoming.capacity"].int?:100)
/**
* Internal method to synchronously send data
*/
protected abstract suspend fun write(data: ByteArray)
/**
* Internal method to receive data synchronously
*/
protected suspend fun receive(data: ByteArray) {
logger.debug { "$this RECEIVED: ${data.decodeToString()}" }
incoming.send(data)
}
private var sendJob: Job? = null
protected abstract fun onOpen()
final override fun open() {
if (!isOpen) {
sendJob = scope.launch {
for (data in outgoing) {
try {
write(data)
logger.debug { "${this@AbstractAsynchronousPort} SENT: ${data.decodeToString()}" }
} catch (ex: Exception) {
if (ex is CancellationException) throw ex
logger.error(ex) { "Error while writing data to the port" }
}
}
}
onOpen()
} else {
logger.warn { "$this already opened" }
}
}
/**
* Send a data packet via the port
*/
override suspend fun send(data: ByteArray) {
outgoing.send(data)
}
/**
* Raw flow of incoming data chunks. The chunks are not guaranteed to be complete phrases.
* To form phrases, some condition should be used on top of it.
* For example [stringsDelimitedIncoming] generates phrases with fixed delimiter.
*/
override fun subscribe(): Flow<ByteArray> = incoming.receiveAsFlow()
override fun close() {
outgoing.close()
incoming.close()
sendJob?.cancel()
}
override fun toString(): String = meta["name"].string?:"ChannelPort[${hashCode().toString(16)}]"
}
/**
* Send UTF-8 encoded string
*/
public suspend fun AsynchronousPort.send(string: String): Unit = send(string.encodeToByteArray())

View File

@@ -0,0 +1,54 @@
package space.kscience.controls.ports
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.string
/**
* A DataForge plugin for managing ports
*/
public class Ports : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
private val synchronousPortFactories by lazy {
context.gather<Factory<SynchronousPort>>(SYNCHRONOUS_PORT_TYPE)
}
private val asynchronousPortFactories by lazy {
context.gather<Factory<AsynchronousPort>>(ASYNCHRONOUS_PORT_TYPE)
}
/**
* Create a new [AsynchronousPort] according to specification
*/
public fun buildAsynchronousPort(meta: Meta): AsynchronousPort {
val type by meta.string { error("Port type is not defined") }
val factory = asynchronousPortFactories.entries
.firstOrNull { it.key.toString() == type }?.value
?: error("Port factory for type $type not found")
return factory.build(context, meta)
}
/**
* Create a [SynchronousPort] according to specification or wrap an asynchronous implementation
*/
public fun buildSynchronousPort(meta: Meta): SynchronousPort {
val type by meta.string { error("Port type is not defined") }
val factory = synchronousPortFactories.entries
.firstOrNull { it.key.toString() == type }?.value
?: return buildAsynchronousPort(meta).asSynchronousPort()
return factory.build(context, meta)
}
public companion object : PluginFactory<Ports> {
override val tag: PluginTag = PluginTag("controls.ports", group = PluginTag.DATAFORGE_GROUP)
public const val ASYNCHRONOUS_PORT_TYPE: String = "controls.asynchronousPort"
public const val SYNCHRONOUS_PORT_TYPE: String = "controls.synchronousPort"
override fun build(context: Context, meta: Meta): Ports = Ports()
}
}

View File

@@ -0,0 +1,101 @@
package space.kscience.controls.ports
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.Buffer
import kotlinx.io.readByteArray
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
/**
* A port handler for synchronous (request-response) communication with a port.
* Only one request could be active at a time (others are suspended).
*/
public interface SynchronousPort : ContextAware, AutoCloseable {
public fun open()
public val isOpen: Boolean
/**
* Send a single message and wait for the flow of response chunks.
* The consumer is responsible for calling a terminal operation on the flow.
*/
public suspend fun <R> respond(
request: ByteArray,
transform: suspend Flow<ByteArray>.() -> R,
): R
/**
* Synchronously read fixed size response to a given [request]. Discard additional response bytes.
*/
public suspend fun respondFixedMessageSize(
request: ByteArray,
responseSize: Int,
): ByteArray = respond(request) {
val buffer = Buffer()
takeWhile {
buffer.size < responseSize
}.collect {
buffer.write(it)
}
buffer.readByteArray(responseSize)
}
}
private class SynchronousOverAsynchronousPort(
val port: AsynchronousPort,
val mutex: Mutex,
) : SynchronousPort {
override val context: Context get() = port.context
override fun open() {
if (!port.isOpen) port.open()
}
override val isOpen: Boolean get() = port.isOpen
override fun close() {
if (port.isOpen) port.close()
}
override suspend fun <R> respond(
request: ByteArray,
transform: suspend Flow<ByteArray>.() -> R,
): R = mutex.withLock {
port.send(request)
transform(port.subscribe())
}
}
/**
* Provide a synchronous wrapper for an asynchronous port.
* Optionally provide external [mutex] for operation synchronization.
*
* If the [AsynchronousPort] is called directly, it could violate [SynchronousPort] contract
* of only one request running simultaneously.
*/
public fun AsynchronousPort.asSynchronousPort(mutex: Mutex = Mutex()): SynchronousPort =
SynchronousOverAsynchronousPort(this, mutex)
/**
* Send request and read incoming data blocks until the delimiter is encountered
*/
public suspend fun SynchronousPort.respondWithDelimiter(
data: ByteArray,
delimiter: ByteArray,
): ByteArray = respond(data) {
withDelimiter(delimiter).first()
}
public suspend fun SynchronousPort.respondStringWithDelimiter(
data: String,
delimiter: String,
): String = respond(data.encodeToByteArray()) {
withStringDelimiter(delimiter).first()
}

View File

@@ -0,0 +1,5 @@
package space.kscience.controls.ports
import space.kscience.dataforge.io.Binary
public fun Binary.readShort(position: Int): Short = read(position) { readShort() }

View File

@@ -0,0 +1,85 @@
package space.kscience.controls.ports
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.transform
import kotlinx.io.Buffer
import kotlinx.io.readByteArray
/**
* Transform byte fragments into complete phrases using given delimiter. Not thread safe.
*
* TODO add type wrapper for phrases
*/
public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray> {
require(delimiter.isNotEmpty()) { "Delimiter must not be empty" }
val output = Buffer()
var matcherPosition = 0
onCompletion {
output.close()
}
return transform { chunk ->
chunk.forEach { byte ->
output.writeByte(byte)
//matching current symbol in delimiter
if (byte == delimiter[matcherPosition]) {
matcherPosition++
if (matcherPosition == delimiter.size) {
//full match achieved, sending result
emit(output.readByteArray())
output.clear()
matcherPosition = 0
}
} else if (matcherPosition > 0) {
//Reset matcher since full match not achieved
matcherPosition = 0
}
}
}
}
private fun Flow<ByteArray>.withFixedMessageSize(messageSize: Int): Flow<ByteArray> {
require(messageSize > 0) { "Message size should be positive" }
val output = Buffer()
onCompletion {
output.close()
}
return transform { chunk ->
val remaining: Int = (messageSize - output.size).toInt()
if (chunk.size >= remaining) {
output.write(chunk, endIndex = remaining)
emit(output.readByteArray())
output.clear()
//write the remaining chunk fragment
if(chunk.size> remaining) {
output.write(chunk, startIndex = remaining)
}
} else {
output.write(chunk)
}
}
}
/**
* Transform byte fragments into utf-8 phrases using utf-8 delimiter
*/
public fun Flow<ByteArray>.withStringDelimiter(delimiter: String): Flow<String> {
return withDelimiter(delimiter.encodeToByteArray()).map { it.decodeToString() }
}
/**
* A flow of delimited phrases
*/
public fun AsynchronousPort.delimitedIncoming(delimiter: ByteArray): Flow<ByteArray> = subscribe().withDelimiter(delimiter)
/**
* A flow of delimited phrases with string content
*/
public fun AsynchronousPort.stringsDelimitedIncoming(delimiter: String): Flow<String> = subscribe().withStringDelimiter(delimiter)

View File

@@ -0,0 +1,233 @@
package space.kscience.controls.spec
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import space.kscience.controls.api.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.debug
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.misc.DFExperimental
import kotlin.coroutines.CoroutineContext
/**
* Write a meta [item] to [device]
*/
@OptIn(InternalDeviceAPI::class)
private suspend fun <D : Device, T> MutableDevicePropertySpec<D, T>.writeMeta(device: D, item: Meta) {
write(device, converter.readOrNull(item) ?: error("Meta $item could not be read with $converter"))
}
/**
* Read Meta item from the [device]
*/
@OptIn(InternalDeviceAPI::class)
private suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? =
read(device)?.let(converter::convert)
private suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
device: D,
item: Meta,
): Meta? {
val arg: I = inputConverter.readOrNull(item) ?: error("Failed to convert $item with $inputConverter")
val res = execute(device, arg)
return res?.let { outputConverter.convert(res) }
}
/**
* A base abstractions for [Device], introducing specifications for properties
*/
public abstract class DeviceBase<D : Device>(
final override val context: Context,
final override val meta: Meta = Meta.EMPTY,
) : CachingDevice {
/**
* Collection of property specifications
*/
public abstract val properties: Map<String, DevicePropertySpec<D, *>>
/**
* Collection of action specifications
*/
public abstract val actions: Map<String, DeviceActionSpec<D, *, *>>
override val propertyDescriptors: Collection<PropertyDescriptor>
get() = properties.values.map { it.descriptor }
override val actionDescriptors: Collection<ActionDescriptor>
get() = actions.values.map { it.descriptor }
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow(
replay = meta["message.buffer"].int ?: 1000,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override val coroutineContext: CoroutineContext = context.newCoroutineContext(
SupervisorJob(context.coroutineContext[Job]) +
CoroutineName("Device $this") +
CoroutineExceptionHandler { _, throwable ->
launch {
sharedMessageFlow.emit(
DeviceErrorMessage(
errorMessage = throwable.message,
errorType = throwable::class.simpleName,
errorStackTrace = throwable.stackTraceToString()
)
)
}
}
)
/**
* Logical state store
*/
private val logicalState: HashMap<String, Meta?> = HashMap()
public override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
@Suppress("UNCHECKED_CAST")
internal val self: D
get() = this as D
private val stateLock = Mutex()
/**
* Update logical property state and notify listeners
*/
protected suspend fun propertyChanged(propertyName: String, value: Meta?) {
if (value != logicalState[propertyName]) {
stateLock.withLock {
logicalState[propertyName] = value
}
if (value != null) {
sharedMessageFlow.emit(PropertyChangedMessage(propertyName, value))
}
}
}
/**
* Notify the device that a property with [spec] value is changed
*/
protected suspend fun <T> propertyChanged(spec: DevicePropertySpec<D, T>, value: T) {
propertyChanged(spec.name, spec.converter.convert(value))
}
/**
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
* The logical state is updated after read
*/
override suspend fun readProperty(propertyName: String): Meta {
val spec = properties[propertyName] ?: error("Property with name $propertyName not found")
val meta = spec.readMeta(self) ?: error("Failed to read property $propertyName")
propertyChanged(propertyName, meta)
return meta
}
/**
* Read property if it exists and read correctly. Return null otherwise.
*/
public suspend fun readPropertyOrNull(propertyName: String): Meta? {
val spec = properties[propertyName] ?: return null
val meta = spec.readMeta(self) ?: return null
propertyChanged(propertyName, meta)
return meta
}
override fun getProperty(propertyName: String): Meta? = logicalState[propertyName]
override suspend fun invalidate(propertyName: String) {
stateLock.withLock {
logicalState.remove(propertyName)
}
}
override suspend fun writeProperty(propertyName: String, value: Meta): Unit {
//bypass property setting if it already has that value
if (logicalState[propertyName] == value) {
logger.debug { "Skipping setting $propertyName to $value because value is already set" }
return
}
when (val property = properties[propertyName]) {
null -> {
//If there are no registered physical properties with given name, write a logical one.
propertyChanged(propertyName, value)
}
is MutableDevicePropertySpec -> {
//if there is a writeable property with a given name, invalidate logical and write physical
invalidate(propertyName)
property.writeMeta(self, value)
// perform read after writing if the writer did not set the value and the value is still in invalid state
if (logicalState[propertyName] == null) {
val meta = property.readMeta(self)
propertyChanged(propertyName, meta)
}
}
else -> {
error("Property $property is not writeable")
}
}
}
override suspend fun execute(actionName: String, argument: Meta?): Meta? {
val spec = actions[actionName] ?: error("Action with name $actionName not found")
return spec.executeWithMeta(self, argument ?: Meta.EMPTY)
}
@DFExperimental
final override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.STOPPED
private set(value) {
if (field != value) {
launch {
sharedMessageFlow.emit(
DeviceLifeCycleMessage(value)
)
}
}
field = value
}
protected open suspend fun onStart() {
}
@OptIn(DFExperimental::class)
final override suspend fun start() {
if (lifecycleState == DeviceLifecycleState.STOPPED) {
super.start()
lifecycleState = DeviceLifecycleState.STARTING
onStart()
lifecycleState = DeviceLifecycleState.STARTED
} else {
logger.debug { "Device $this is already started" }
}
}
protected open fun onStop() {
}
@OptIn(DFExperimental::class)
final override fun stop() {
onStop()
lifecycleState = DeviceLifecycleState.STOPPED
super.stop()
}
abstract override fun toString(): String
}

View File

@@ -0,0 +1,29 @@
package space.kscience.controls.spec
import space.kscience.controls.api.Device
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta
/**
* A device generated from specification
* @param D recursive self-type for properties and actions
*/
public open class DeviceBySpec<D : Device>(
public val spec: DeviceSpec<in D>,
context: Context,
meta: Meta = Meta.EMPTY,
) : DeviceBase<D>(context, meta) {
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
override suspend fun onStart(): Unit = with(spec) {
self.onOpen()
}
override fun onStop(): Unit = with(spec){
self.onClose()
}
override fun toString(): String = "Device(spec=$spec)"
}

View File

@@ -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.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
}

View File

@@ -0,0 +1,161 @@
package space.kscience.controls.spec
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import space.kscience.controls.api.*
import space.kscience.dataforge.meta.MetaConverter
/**
* This API is internal and should not be used in user code
*/
@RequiresOptIn("This API should not be called outside of Device internals")
public annotation class InternalDeviceAPI
/**
* Specification for a device read-only property
*/
public interface DevicePropertySpec<in D, T> {
/**
* Property descriptor
*/
public val descriptor: PropertyDescriptor
/**
* Meta item converter for the resulting type
*/
public val converter: MetaConverter<T>
/**
* Read physical value from the given [device]
*/
@InternalDeviceAPI
public suspend fun read(device: D): T?
}
/**
* Property name should be unique in a device
*/
public val DevicePropertySpec<*, *>.name: String get() = descriptor.name
public interface MutableDevicePropertySpec<in D : Device, T> : DevicePropertySpec<D, T> {
/**
* Write physical value to a device
*/
@InternalDeviceAPI
public suspend fun write(device: D, value: T)
}
public interface DeviceActionSpec<in D, I, O> {
/**
* Action descriptor
*/
public val descriptor: ActionDescriptor
public val inputConverter: MetaConverter<I>
public val outputConverter: MetaConverter<O>
/**
* Execute action on a device
*/
public suspend fun execute(device: D, input: I): O
}
/**
* Action name. Should be unique in the device
*/
public val DeviceActionSpec<*, *, *>.name: String get() = descriptor.name
public suspend fun <T, D : Device> D.read(propertySpec: DevicePropertySpec<D, T>): T =
propertySpec.converter.readOrNull(readProperty(propertySpec.name)) ?: error("Property read result is not valid")
/**
* Read typed value and update/push event if needed.
* Return null if property read is not successful or property is undefined.
*/
public suspend fun <T, D : DeviceBase<D>> D.readOrNull(propertySpec: DevicePropertySpec<D, T>): T? =
readPropertyOrNull(propertySpec.name)?.let(propertySpec.converter::readOrNull)
public suspend fun <T, D : Device> D.getOrRead(propertySpec: DevicePropertySpec<D, T>): T =
propertySpec.converter.read(getOrReadProperty(propertySpec.name))
/**
* Write typed property state and invalidate logical state
*/
public suspend fun <T, D : Device> D.write(propertySpec: MutableDevicePropertySpec<D, T>, value: T) {
writeProperty(propertySpec.name, propertySpec.converter.convert(value))
}
/**
* Fire and forget variant of property writing. Actual write is performed asynchronously on a [Device] scope
*/
public fun <T, D : Device> D.writeAsync(propertySpec: MutableDevicePropertySpec<D, T>, value: T): Job = launch {
write(propertySpec, value)
}
/**
* A type safe flow of property changes for given property
*/
public fun <D : Device, T> D.propertyFlow(spec: DevicePropertySpec<D, T>): Flow<T> = messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == spec.name }
.mapNotNull { spec.converter.read(it.value) }
/**
* A type safe property change listener. Uses the device [CoroutineScope].
*/
public fun <D : Device, T> D.onPropertyChange(
spec: DevicePropertySpec<D, T>,
scope: CoroutineScope = this,
callback: suspend PropertyChangedMessage.(T) -> Unit,
): Job = messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == spec.name }
.onEach { change ->
val newValue = spec.converter.read(change.value)
if (newValue != null) {
change.callback(newValue)
}
}.launchIn(scope)
/**
* Call [callback] on initial property value and each value change
*/
public fun <D : Device, T> D.useProperty(
spec: DevicePropertySpec<D, T>,
scope: CoroutineScope = this,
callback: suspend (T) -> Unit,
): Job = scope.launch {
callback(read(spec))
messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == spec.name }
.collect { change ->
val newValue = spec.converter.readOrNull(change.value)
if (newValue != null) {
callback(newValue)
}
}
}
/**
* Reset the logical state of a property
*/
public suspend fun <D : CachingDevice> D.invalidate(propertySpec: DevicePropertySpec<D, *>) {
invalidate(propertySpec.name)
}
/**
* Execute the action with name according to [actionSpec]
*/
public suspend fun <I, O, D : Device> D.execute(actionSpec: DeviceActionSpec<D, I, O>, input: I): O =
actionSpec.execute(this, input)
public suspend fun <O, D : Device> D.execute(actionSpec: DeviceActionSpec<D, Unit, O>): O =
actionSpec.execute(this, Unit)

View File

@@ -0,0 +1,173 @@
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.MetaConverter
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
public object UnitMetaConverter : MetaConverter<Unit> {
override fun readOrNull(source: Meta): Unit = Unit
override fun convert(obj: Unit): Meta = Meta.EMPTY
}
public val MetaConverter.Companion.unit: MetaConverter<Unit> get() = UnitMetaConverter
@OptIn(InternalDeviceAPI::class)
public abstract class DeviceSpec<D : Device> {
//initializing the metadata property for everyone
private val _properties = hashMapOf<String, DevicePropertySpec<D, *>>(
DeviceMetaPropertySpec.name to DeviceMetaPropertySpec
)
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
public open suspend fun D.onOpen() {
}
public open fun D.onClose() {
}
public fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
_properties[deviceProperty.name] = deviceProperty
return deviceProperty
}
public fun <T> property(
converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> T?,
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> =
PropertyDelegateProvider { _: DeviceSpec<D>, property ->
val propertyName = name ?: property.name
val deviceProperty = object : DevicePropertySpec<D, T> {
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
fromSpec(property)
descriptorBuilder()
}
override val converter: MetaConverter<T> = converter
override suspend fun read(device: D): T? =
withContext(device.coroutineContext) { device.read(propertyName) }
}
registerProperty(deviceProperty)
ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>> { _, _ ->
deviceProperty
}
}
public fun <T> mutableProperty(
converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> T?,
write: suspend D.(propertyName: String, value: T) -> Unit,
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
val propertyName = name ?: property.name
val deviceProperty = object : MutableDevicePropertySpec<D, T> {
override val descriptor: PropertyDescriptor = PropertyDescriptor(
propertyName,
mutable = true
).apply {
fromSpec(property)
descriptorBuilder()
}
override val converter: MetaConverter<T> = converter
override suspend fun read(device: D): T? =
withContext(device.coroutineContext) { device.read(propertyName) }
override suspend fun write(device: D, value: T): Unit = withContext(device.coroutineContext) {
device.write(propertyName, value)
}
}
registerProperty(deviceProperty)
ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>> { _, _ ->
deviceProperty
}
}
public fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
_actions[deviceAction.name] = deviceAction
return deviceAction
}
public fun <I, O> action(
inputConverter: MetaConverter<I>,
outputConverter: MetaConverter<O>,
descriptorBuilder: ActionDescriptor.() -> Unit = {},
name: String? = null,
execute: suspend D.(I) -> O,
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>>> =
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
val actionName = name ?: property.name
val deviceAction = object : DeviceActionSpec<D, I, O> {
override val descriptor: ActionDescriptor = ActionDescriptor(actionName).apply {
fromSpec(property)
descriptorBuilder()
}
override val inputConverter: MetaConverter<I> = inputConverter
override val outputConverter: MetaConverter<O> = outputConverter
override suspend fun execute(device: D, input: I): O = withContext(device.coroutineContext) {
device.execute(input)
}
}
_actions[actionName] = deviceAction
ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, I, O>> { _, _ ->
deviceAction
}
}
}
/**
* An action that takes no parameters and returns no values
*/
public fun <D : Device> DeviceSpec<D>.unitAction(
descriptorBuilder: ActionDescriptor.() -> Unit = {},
name: String? = null,
execute: suspend D.() -> Unit,
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Unit, Unit>>> =
action(
MetaConverter.Companion.unit,
MetaConverter.Companion.unit,
descriptorBuilder,
name
) {
execute()
}
/**
* An action that takes [Meta] and returns [Meta]. No conversions are done
*/
public fun <D : Device> DeviceSpec<D>.metaAction(
descriptorBuilder: ActionDescriptor.() -> Unit = {},
name: String? = null,
execute: suspend D.(Meta) -> Meta,
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceActionSpec<D, Meta, Meta>>> =
action(
MetaConverter.Companion.meta,
MetaConverter.Companion.meta,
descriptorBuilder,
name
) {
execute(it)
}

View File

@@ -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 caller context. To call it on device context, use `flowOn(coroutineContext)`.
*
* The flow is canceled when the device scope is canceled
*/
public fun <D : Device, R> D.readRecurring(interval: Duration, 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()
}
}
}

View File

@@ -0,0 +1,12 @@
package space.kscience.controls.spec
import space.kscience.controls.api.ActionDescriptor
import space.kscience.controls.api.PropertyDescriptor
import kotlin.reflect.KProperty
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FIELD)
public annotation class Description(val content: String)
internal expect fun PropertyDescriptor.fromSpec(property: KProperty<*>)
internal expect fun ActionDescriptor.fromSpec(property: KProperty<*>)

View File

@@ -0,0 +1,22 @@
package space.kscience.controls.spec
import space.kscience.dataforge.meta.*
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 readOrNull(source: Meta): Duration = source.value?.double?.toDuration(DurationUnit.SECONDS)
?: run {
val unit: DurationUnit = source["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
val value = source[Meta.VALUE_KEY].double ?: error("No value present for Duration")
return@run value.toDuration(unit)
}
override fun convert(obj: Duration): Meta = obj.toDouble(DurationUnit.SECONDS).asMeta()
}
public val MetaConverter.Companion.duration: MetaConverter<Duration> get() = DurationConverter

View File

@@ -0,0 +1,194 @@
package space.kscience.controls.spec
import space.kscience.controls.api.Device
import space.kscience.controls.api.PropertyDescriptor
import space.kscience.controls.api.metaDescriptor
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaConverter
import space.kscience.dataforge.meta.ValueType
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
/**
* A read-only device property that delegates reading to a device [KProperty1]
*/
public fun <T, D : Device> DeviceSpec<D>.property(
converter: MetaConverter<T>,
readOnlyProperty: KProperty1<D, T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, T>>> = property(
converter,
descriptorBuilder,
name = readOnlyProperty.name,
read = { readOnlyProperty.get(this) }
)
/**
* Mutable property that delegates reading and writing to a device [KMutableProperty1]
*/
public fun <T, D : Device> DeviceSpec<D>.mutableProperty(
converter: MetaConverter<T>,
readWriteProperty: KMutableProperty1<D, T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
mutableProperty(
converter,
descriptorBuilder,
readWriteProperty.name,
read = { _ -> readWriteProperty.get(this) },
write = { _, value: T -> readWriteProperty.set(this, value) }
)
/**
* Register a mutable logical property (without a corresponding physical state) for a device
*/
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.logicalProperty(
converter: MetaConverter<T>,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, T>>> =
mutableProperty(
converter,
descriptorBuilder,
name,
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
write = { propertyName, value -> writeProperty(propertyName, converter.convert(value)) }
)
//read only delegates
public fun <D : Device> DeviceSpec<D>.booleanProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Boolean?
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> = property(
MetaConverter.boolean,
{
metaDescriptor {
valueType(ValueType.BOOLEAN)
}
descriptorBuilder()
},
name,
read
)
private inline fun numberDescriptor(
crossinline descriptorBuilder: PropertyDescriptor.() -> Unit = {}
): PropertyDescriptor.() -> Unit = {
metaDescriptor {
valueType(ValueType.NUMBER)
}
descriptorBuilder()
}
public fun <D : Device> DeviceSpec<D>.numberProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Number?
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Number>>> = property(
MetaConverter.number,
numberDescriptor(descriptorBuilder),
name,
read
)
public fun <D : Device> DeviceSpec<D>.doubleProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Double?
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> = property(
MetaConverter.double,
numberDescriptor(descriptorBuilder),
name,
read
)
public fun <D : Device> DeviceSpec<D>.stringProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> String?
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> = property(
MetaConverter.string,
{
metaDescriptor {
valueType(ValueType.STRING)
}
descriptorBuilder()
},
name,
read
)
public fun <D : Device> DeviceSpec<D>.metaProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Meta?
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Meta>>> = property(
MetaConverter.meta,
{
metaDescriptor {
valueType(ValueType.STRING)
}
descriptorBuilder()
},
name,
read
)
//read-write delegates
public fun <D : Device> DeviceSpec<D>.booleanProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Boolean?,
write: suspend D.(propertyName: String, value: Boolean) -> Unit
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Boolean>>> =
mutableProperty(
MetaConverter.boolean,
{
metaDescriptor {
valueType(ValueType.BOOLEAN)
}
descriptorBuilder()
},
name,
read,
write
)
public fun <D : Device> DeviceSpec<D>.numberProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Number,
write: suspend D.(propertyName: String, value: Number) -> Unit
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Number>>> =
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
public fun <D : Device> DeviceSpec<D>.doubleProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Double,
write: suspend D.(propertyName: String, value: Double) -> Unit
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Double>>> =
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
public fun <D : Device> DeviceSpec<D>.stringProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> String,
write: suspend D.(propertyName: String, value: String) -> Unit
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, String>>> =
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
public fun <D : Device> DeviceSpec<D>.metaProperty(
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
name: String? = null,
read: suspend D.(propertyName: String) -> Meta,
write: suspend D.(propertyName: String, value: Meta) -> Unit
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)

View File

@@ -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)
}
}

View File

@@ -0,0 +1,9 @@
package space.kscience.controls.spec
import space.kscience.controls.api.ActionDescriptor
import space.kscience.controls.api.PropertyDescriptor
import kotlin.reflect.KProperty
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>){}
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}

View File

@@ -0,0 +1,175 @@
package space.kscience.controls.ports
import kotlinx.coroutines.*
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.*
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousCloseException
import java.nio.channels.ByteChannel
import java.nio.channels.DatagramChannel
import java.nio.channels.SocketChannel
import kotlin.coroutines.CoroutineContext
/**
* Copy the contents of this buffer to an array
*/
public fun ByteBuffer.copyToArray(limit: Int = limit()): ByteArray {
rewind()
val response = ByteArray(limit)
get(response)
rewind()
return response
}
/**
* A port based on nio [ByteChannel]
*/
public class ChannelPort(
context: Context,
meta: Meta,
coroutineContext: CoroutineContext = context.coroutineContext,
channelBuilder: suspend () -> ByteChannel,
) : AbstractAsynchronousPort(context, meta, coroutineContext), AutoCloseable {
/**
* A handler to await port connection
*/
private val futureChannel: Deferred<ByteChannel> = scope.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
channelBuilder()
}
private var listenerJob: Job? = null
override val isOpen: Boolean get() = listenerJob?.isActive == true
override fun onOpen() {
listenerJob = scope.launch(Dispatchers.IO) {
val channel = futureChannel.await()
val buffer = ByteBuffer.allocate(1024)
while (isActive && channel.isOpen) {
try {
val num = channel.read(buffer)
if (num > 0) {
receive(buffer.copyToArray(num))
}
if (num < 0) cancel("The input channel is exhausted")
} catch (ex: Exception) {
if (ex is AsynchronousCloseException) {
logger.info { "Channel $channel closed" }
} else {
logger.error(ex) { "Channel read error, retrying in 1 second" }
delay(1000)
}
}
}
}
}
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
futureChannel.await().write(ByteBuffer.wrap(data))
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun close() {
listenerJob?.cancel()
if (futureChannel.isCompleted) {
futureChannel.getCompleted().close()
}
super.close()
}
}
/**
* A [Factory] for TCP connections
*/
public object TcpPort : Factory<AsynchronousPort> {
public fun build(
context: Context,
host: String,
port: Int,
coroutineContext: CoroutineContext = context.coroutineContext,
): ChannelPort {
val meta = Meta {
"name" put "tcp://$host:$port"
"type" put "tcp"
"host" put host
"port" put port
}
return ChannelPort(context, meta, coroutineContext) {
SocketChannel.open(InetSocketAddress(host, port))
}
}
/**
* Create and open TCP port
*/
public fun open(
context: Context,
host: String,
port: Int,
coroutineContext: CoroutineContext = context.coroutineContext,
): ChannelPort = build(context, host, port, coroutineContext).apply { open() }
override fun build(context: Context, meta: Meta): ChannelPort {
val host = meta["host"].string ?: "localhost"
val port = meta["port"].int ?: error("Port value for TCP port is not defined in $meta")
return build(context, host, port)
}
}
/**
* A [Factory] for UDP connections
*/
public object UdpPort : Factory<AsynchronousPort> {
public fun build(
context: Context,
remoteHost: String,
remotePort: Int,
localPort: Int? = null,
localHost: String? = null,
coroutineContext: CoroutineContext = context.coroutineContext,
): ChannelPort {
val meta = Meta {
"name" put "udp://$remoteHost:$remotePort"
"type" put "udp"
"remoteHost" put remoteHost
"remotePort" put remotePort
localHost?.let { "localHost" put it }
localPort?.let { "localPort" put it }
}
return ChannelPort(context, meta, coroutineContext) {
DatagramChannel.open().apply {
//bind the channel to a local port to receive messages
localPort?.let { bind(InetSocketAddress(localHost ?: "localhost", it)) }
//connect to remote port to send messages
connect(InetSocketAddress(remoteHost, remotePort.toInt()))
context.logger.info { "Connected to UDP $remotePort on $remoteHost" }
}
}
}
/**
* Connect a datagram channel to a remote host/port. If [localPort] is provided, it is used to bind local port for receiving messages.
*/
public fun open(
context: Context,
remoteHost: String,
remotePort: Int,
localPort: Int? = null,
localHost: String = "localhost",
): ChannelPort = build(context, remoteHost, remotePort, localPort, localHost).apply { open() }
override fun build(context: Context, meta: Meta): ChannelPort {
val remoteHost by meta.string { error("Remote host is not specified") }
val remotePort by meta.number { error("Remote port is not specified") }
val localHost: String? by meta.string()
val localPort: Int? by meta.int()
return build(context, remoteHost, remotePort.toInt(), localPort, localHost)
}
}

View File

@@ -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.asName
/**
* A plugin for loading JVM nio-based ports
*/
public class JvmPortsPlugin : AbstractPlugin() {
public val ports: Ports by require(Ports)
override val tag: PluginTag get() = Companion.tag
override fun content(target: String): Map<Name, Any> = when(target){
Ports.ASYNCHRONOUS_PORT_TYPE -> mapOf(
"tcp".asName() to TcpPort,
"udp".asName() to UdpPort
)
else -> emptyMap()
}
public companion object : PluginFactory<JvmPortsPlugin> {
override val tag: PluginTag = PluginTag("controls.ports.jvm", group = PluginTag.DATAFORGE_GROUP)
override fun build(context: Context, meta: Meta): JvmPortsPlugin = JvmPortsPlugin()
}
}

View File

@@ -0,0 +1,59 @@
package space.kscience.controls.ports
import kotlinx.coroutines.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta
import java.net.DatagramPacket
import java.net.DatagramSocket
import kotlin.coroutines.CoroutineContext
/**
* A port based on [DatagramSocket] for cases, where [ChannelPort] does not work for some reason
*/
public class UdpSocketPort(
override val context: Context,
meta: Meta,
private val socket: DatagramSocket,
coroutineContext: CoroutineContext = context.coroutineContext,
) : AbstractAsynchronousPort(context, meta, coroutineContext) {
private var listenerJob: Job? = null
override fun onOpen() {
listenerJob = context.launch(Dispatchers.IO) {
while (isActive) {
val buf = ByteArray(socket.receiveBufferSize)
val packet = DatagramPacket(
buf,
buf.size,
)
socket.receive(packet)
val bytes = packet.data.copyOfRange(
packet.offset,
packet.offset + packet.length
)
receive(bytes)
}
}
}
override fun close() {
listenerJob?.cancel()
super.close()
}
override val isOpen: Boolean get() = listenerJob?.isActive == true
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
val packet = DatagramPacket(
data,
data.size,
socket.remoteSocketAddress
)
socket.send(packet)
}
}

View File

@@ -0,0 +1,18 @@
package space.kscience.controls.spec
import space.kscience.controls.api.ActionDescriptor
import space.kscience.controls.api.PropertyDescriptor
import kotlin.reflect.KProperty
import kotlin.reflect.full.findAnnotation
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>) {
property.findAnnotation<Description>()?.let {
description = it.content
}
}
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){
property.findAnnotation<Description>()?.let {
description = it.content
}
}

View File

@@ -0,0 +1,50 @@
package space.kscience.controls.ports
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import space.kscience.dataforge.context.Global
import kotlin.test.assertEquals
internal class AsynchronousPortIOTest {
@Test
fun testDelimiteredByteArrayFlow() {
val flow = flowOf("bb?b", "ddd?", ":defgb?:ddf", "34fb?:--").map { it.encodeToByteArray() }
val chunked = flow.withDelimiter("?:".encodeToByteArray())
runBlocking {
val result = chunked.toList()
assertEquals(3, result.size)
assertEquals("bb?bddd?:", result[0].decodeToString())
assertEquals("defgb?:", result[1].decodeToString())
assertEquals("ddf34fb?:", result[2].decodeToString())
}
}
@Test
fun testUdpCommunication() = runTest {
val receiver = UdpPort.open(Global, "localhost", 8811, localPort = 8812)
val sender = UdpPort.open(Global, "localhost", 8812, localPort = 8811)
delay(30)
repeat(10) {
sender.send("Line number $it\n")
}
val res = receiver
.subscribe()
.withStringDelimiter("\n")
.take(10)
.toList()
assertEquals("Line number 3", res[3].trim())
receiver.close()
sender.close()
}
}

View File

@@ -0,0 +1,9 @@
package space.kscience.controls.spec
import space.kscience.controls.api.ActionDescriptor
import space.kscience.controls.api.PropertyDescriptor
import kotlin.reflect.KProperty
internal actual fun PropertyDescriptor.fromSpec(property: KProperty<*>) {}
internal actual fun ActionDescriptor.fromSpec(property: KProperty<*>){}

View File

@@ -0,0 +1,21 @@
# Module controls-jupyter
## Usage
## Artifact:
The Maven coordinates of this project are `space.kscience:controls-jupyter:0.3.0`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:controls-jupyter:0.3.0")
}
```

View File

@@ -0,0 +1,8 @@
public final class space/kscience/controls/jupyter/ControlsJupyter : space/kscience/visionforge/jupyter/VisionForgeIntegration {
public static final field Companion Lspace/kscience/controls/jupyter/ControlsJupyter$Companion;
public fun <init> ()V
}
public final class space/kscience/controls/jupyter/ControlsJupyter$Companion {
}

View File

@@ -0,0 +1,18 @@
plugins {
id("space.kscience.gradle.mpp")
`maven-publish`
}
kscience {
fullStack("js/controls-jupyter.js")
useKtor()
useContextReceivers()
jupyterLibrary("space.kscience.controls.jupyter.ControlsJupyter")
dependencies {
implementation(projects.controlsVision)
implementation(libs.visionforge.jupiter)
}
jvmMain {
implementation(spclibs.logback.classic)
}
}

View File

@@ -0,0 +1,14 @@
import space.kscience.visionforge.html.runVisionClient
import space.kscience.visionforge.jupyter.VFNotebookClient
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
public fun main(): Unit = runVisionClient {
// plugin(DeviceManager)
// plugin(ClockManager)
plugin(PlotlyPlugin)
plugin(MarkupPlugin)
// plugin(TableVisionJsPlugin)
plugin(VFNotebookClient)
}

View File

@@ -0,0 +1,71 @@
package space.kscience.controls.jupyter
import org.jetbrains.kotlinx.jupyter.api.declare
import org.jetbrains.kotlinx.jupyter.api.libraries.resources
import space.kscience.controls.manager.ClockManager
import space.kscience.controls.manager.DeviceManager
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.plotly.Plot
import space.kscience.visionforge.jupyter.VisionForge
import space.kscience.visionforge.jupyter.VisionForgeIntegration
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.asVision
import space.kscience.visionforge.visionManager
@OptIn(DFExperimental::class)
public class ControlsJupyter : VisionForgeIntegration(CONTEXT.visionManager) {
override fun Builder.afterLoaded(vf: VisionForge) {
resources {
js("controls-jupyter") {
classPath("js/controls-jupyter.js")
}
}
onLoaded {
declare("context" to CONTEXT)
}
import(
"kotlin.time.*",
"kotlin.time.Duration.Companion.milliseconds",
"kotlin.time.Duration.Companion.seconds",
// "space.kscience.tables.*",
"space.kscience.dataforge.meta.*",
"space.kscience.dataforge.context.*",
"space.kscience.plotly.*",
"space.kscience.plotly.models.*",
"space.kscience.visionforge.plotly.*",
"space.kscience.controls.manager.*",
"space.kscience.controls.constructor.*",
"space.kscience.controls.vision.*",
"space.kscience.controls.spec.*"
)
// render<Table<*>> { table ->
// vf.produceHtml {
// vision { table.toVision() }
// }
// }
render<Plot> { plot ->
vf.produceHtml {
vision { plot.asVision() }
}
}
}
public companion object {
private val CONTEXT: Context = Context("controls-jupyter") {
plugin(DeviceManager)
plugin(ClockManager)
plugin(PlotlyPlugin)
// plugin(TableVisionPlugin)
plugin(MarkupPlugin)
}
}
}

27
controls-magix/README.md Normal file
View File

@@ -0,0 +1,27 @@
# 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.3.0`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:controls-magix:0.3.0")
}
```

View 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;
}

View File

@@ -0,0 +1,40 @@
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()
useCoroutines("1.8.0")
useSerialization {
json()
}
dependencies {
api(projects.magix.magixApi)
api(projects.controlsCore)
api(libs.uuid)
}
}
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()
}
}

View File

@@ -0,0 +1,182 @@
package space.kscience.controls.client
import com.benasher44.uuid.uuid4
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import space.kscience.controls.api.*
import space.kscience.controls.manager.DeviceManager
import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.name
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.magix.api.MagixEndpoint
import space.kscience.magix.api.send
import space.kscience.magix.api.subscribe
import kotlin.coroutines.CoroutineContext
private fun stringUID() = uuid4().leastSignificantBits.toString(16)
/**
* A remote accessible device that relies on connection via Magix
*/
public class DeviceClient(
override val context: Context,
private val deviceName: Name,
incomingFlow: Flow<DeviceMessage>,
private val send: suspend (DeviceMessage) -> Unit,
) : CachingDevice {
override val coroutineContext: CoroutineContext = context.coroutineContext + Job(context.coroutineContext[Job])
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.STARTED
}
/**
* Connect to a remote device via this endpoint.
*
* @param context a [Context] to run device in
* @param sourceEndpointName the name of this endpoint
* @param targetEndpointName the name of endpoint in Magix to connect to
* @param deviceName the name of device within endpoint
*/
public fun MagixEndpoint.remoteDevice(
context: Context,
sourceEndpointName: String,
targetEndpointName: String,
deviceName: Name,
): DeviceClient {
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(targetEndpointName)).map { it.second }
return DeviceClient(context, deviceName, subscription) {
send(
format = DeviceManager.magixFormat,
payload = it,
source = sourceEndpointName,
target = targetEndpointName,
id = stringUID()
)
}
}
/**
* Subscribe on specific property of a device without creating a device
*/
public fun <T> MagixEndpoint.controlsPropertyFlow(
endpointName: String,
deviceName: Name,
propertySpec: DevicePropertySpec<*, T>,
): Flow<T> {
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(endpointName)).map { it.second }
return subscription.filterIsInstance<PropertyChangedMessage>()
.filter { message ->
message.sourceDevice == deviceName && message.property == propertySpec.name
}.map {
propertySpec.converter.read(it.value)
}
}
public suspend fun <T> MagixEndpoint.sendControlsPropertyChange(
sourceEndpointName: String,
targetEndpointName: String,
deviceName: Name,
propertySpec: DevicePropertySpec<*, T>,
value: T,
) {
val message = PropertySetMessage(
property = propertySpec.name,
value = propertySpec.converter.convert(value),
targetDevice = deviceName
)
send(DeviceManager.magixFormat, message, source = sourceEndpointName, target = targetEndpointName)
}
/**
* Subscribe on property change messages together with property values
*/
public fun <T> MagixEndpoint.controlsPropertyMessageFlow(
endpointName: String,
deviceName: Name,
propertySpec: DevicePropertySpec<*, T>,
): Flow<Pair<PropertyChangedMessage, T>> {
val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(endpointName)).map { it.second }
return subscription.filterIsInstance<PropertyChangedMessage>()
.filter { message ->
message.sourceDevice == deviceName && message.property == propertySpec.name
}.map {
it to propertySpec.converter.read(it.value)
}
}

View File

@@ -0,0 +1,79 @@
package space.kscience.controls.client
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import space.kscience.controls.api.PropertyChangedMessage
import space.kscience.controls.api.getOrReadProperty
import space.kscience.controls.spec.DeviceActionSpec
import space.kscience.controls.spec.DevicePropertySpec
import space.kscience.controls.spec.MutableDevicePropertySpec
import space.kscience.controls.spec.name
import space.kscience.dataforge.meta.Meta
/**
* An accessor that allows DeviceClient to connect to any property without type checks
*/
public suspend fun <T> DeviceClient.read(propertySpec: DevicePropertySpec<*, T>): T =
propertySpec.converter.readOrNull(readProperty(propertySpec.name)) ?: error("Property read result is not valid")
public suspend fun <T> DeviceClient.request(propertySpec: DevicePropertySpec<*, T>): T =
propertySpec.converter.read(getOrReadProperty(propertySpec.name))
public suspend fun <T> DeviceClient.write(propertySpec: MutableDevicePropertySpec<*, T>, value: T) {
writeProperty(propertySpec.name, propertySpec.converter.convert(value))
}
public fun <T> DeviceClient.writeAsync(propertySpec: MutableDevicePropertySpec<*, T>, value: T): Job = launch {
write(propertySpec, value)
}
public fun <T> DeviceClient.propertyFlow(spec: DevicePropertySpec<*, T>): Flow<T> = messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == spec.name }
.mapNotNull { spec.converter.readOrNull(it.value) }
public fun <T> DeviceClient.onPropertyChange(
spec: DevicePropertySpec<*, T>,
scope: CoroutineScope = this,
callback: suspend PropertyChangedMessage.(T) -> Unit,
): Job = messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == spec.name }
.onEach { change ->
val newValue = spec.converter.readOrNull(change.value)
if (newValue != null) {
change.callback(newValue)
}
}.launchIn(scope)
public fun <T> DeviceClient.useProperty(
spec: DevicePropertySpec<*, T>,
scope: CoroutineScope = this,
callback: suspend (T) -> Unit,
): Job = scope.launch {
callback(read(spec))
messageFlow
.filterIsInstance<PropertyChangedMessage>()
.filter { it.property == spec.name }
.collect { change ->
val newValue = spec.converter.readOrNull(change.value)
if (newValue != null) {
callback(newValue)
}
}
}
public suspend fun <I, O> DeviceClient.execute(actionSpec: DeviceActionSpec<*, I, O>, input: I): O {
val inputMeta = actionSpec.inputConverter.convert(input)
val res = execute(actionSpec.name, inputMeta)
return actionSpec.outputConverter.read(res ?: Meta.EMPTY)
}
public suspend fun <O> DeviceClient.execute(actionSpec: DeviceActionSpec<*, Unit, O>): O {
val res = execute(actionSpec.name, Meta.EMPTY)
return actionSpec.outputConverter.read(res ?: Meta.EMPTY)
}

View File

@@ -0,0 +1,73 @@
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.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
internal val controlsMagixFormat: MagixFormat<DeviceMessage> = MagixFormat(
DeviceMessage.serializer(),
setOf("controls-kt")
)
/**
* A magix message format to work with Controls-kt data
*/
public val DeviceManager.Companion.magixFormat: MagixFormat<DeviceMessage> get() = controlsMagixFormat
internal fun generateId(request: MagixMessage): String = if (request.id != null) {
"${request.id}.response"
} else {
"controls[${request.payload.hashCode().toString(16)}"
}
/**
* Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1)
*
* Accepts messages with target that equals [endpointID] or null (broadcast messages)
*/
public fun DeviceManager.launchMagixService(
endpoint: MagixEndpoint,
endpointID: String = controlsMagixFormat.defaultFormat,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
): Job = context.launch(coroutineContext) {
endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID, null)).onEach { (request, payload) ->
val responsePayload = respondHubMessage(payload)
responsePayload.forEach {
endpoint.send(
format = controlsMagixFormat,
payload = it,
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().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)
}

View File

@@ -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?
)

View File

@@ -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