API refactoring

This commit is contained in:
Alexander Nozik 2020-08-28 21:54:56 +03:00
parent 3f8d62ebc4
commit 6500d5a05e
11 changed files with 170 additions and 171 deletions

View File

@ -1,5 +1,4 @@
val dataforgeVersion by extra("0.1.9-dev") val dataforgeVersion by extra("0.1.8")
allprojects { allprojects {
repositories { repositories {

View File

@ -8,7 +8,7 @@ plugins {
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
useCoroutines(version = "1.3.7") useCoroutines()
useSerialization() useSerialization()
kotlin { kotlin {
@ -28,6 +28,5 @@ kotlin {
dependencies{ dependencies{
} }
} }
val nativeMain by getting{}
} }
} }

View File

@ -1,30 +1,26 @@
package hep.dataforge.control.api package hep.dataforge.control.api
import hep.dataforge.control.api.Device.Companion.ACTION_LIST_ACTION
import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET
import hep.dataforge.control.api.Device.Companion.EXECUTE_ACTION
import hep.dataforge.control.api.Device.Companion.GET_PROPERTY_ACTION
import hep.dataforge.control.api.Device.Companion.PROPERTY_LIST_ACTION
import hep.dataforge.control.api.Device.Companion.SET_PROPERTY_ACTION
import hep.dataforge.control.controllers.DeviceMessage import hep.dataforge.control.controllers.DeviceMessage
import hep.dataforge.control.controllers.MessageData import hep.dataforge.control.controllers.MessageData
import hep.dataforge.control.controllers.wrap
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.EnvelopeBuilder
import hep.dataforge.io.SimpleEnvelope import hep.dataforge.meta.*
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.wrap
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.io.Binary
import kotlinx.io.Closeable import kotlinx.io.Closeable
interface Consumer {
fun consume(message: Envelope): Unit
}
/** /**
* General interface describing a managed Device * General interface describing a managed Device
*/ */
@Type(DEVICE_TARGET) @Type(DEVICE_TARGET)
interface Device: Closeable, Responder { interface Device: Closeable{
/** /**
* List of supported property descriptors * List of supported property descriptors
*/ */
@ -74,13 +70,12 @@ interface Device: Closeable, Responder {
* Send an action request and suspend caller while request is being processed. * Send an action request and suspend caller while request is being processed.
* Could return null if request does not return a meaningful answer. * Could return null if request does not return a meaningful answer.
*/ */
suspend fun exec(action: String, argument: MetaItem<*>? = null): MetaItem<*>? suspend fun execute(action: String, argument: MetaItem<*>? = null): MetaItem<*>?
override suspend fun respond(request: Envelope): Envelope { /**
val requestMessage = DeviceMessage.wrap(request.meta) *
val responseMessage = respondMessage(requestMessage) */
return SimpleEnvelope(responseMessage.toMeta(), Binary.EMPTY) suspend fun respondWithData(request: Envelope): EnvelopeBuilder = error("Respond with data not implemented")
}
override fun close() { override fun close() {
scope.cancel("The device is closed") scope.cancel("The device is closed")
@ -93,18 +88,42 @@ interface Device: Closeable, Responder {
const val EXECUTE_ACTION = "execute" const val EXECUTE_ACTION = "execute"
const val PROPERTY_LIST_ACTION = "propertyList" const val PROPERTY_LIST_ACTION = "propertyList"
const val ACTION_LIST_ACTION = "actionList" const val ACTION_LIST_ACTION = "actionList"
internal suspend fun respond(device: Device, deviceTarget: String, request: Envelope): Envelope {
val target = request.meta["target"].string
return try {
if (request.data == null) {
respondMessage(device, deviceTarget, DeviceMessage.wrap(request.meta)).wrap()
} else if (target != null && target != deviceTarget) {
error("Wrong target name $deviceTarget expected but $target found")
} else {
val response = device.respondWithData(request).apply {
meta {
"target" put request.meta["source"].string
"source" put deviceTarget
}
}
return response.build()
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}.wrap()
} }
} }
suspend fun Device.respondMessage( internal suspend fun respondMessage(
device: Device,
deviceTarget: String,
request: DeviceMessage request: DeviceMessage
): DeviceMessage { ): DeviceMessage {
return try {
val result: List<MessageData> = when (val action = request.type) { val result: List<MessageData> = when (val action = request.type) {
GET_PROPERTY_ACTION -> { GET_PROPERTY_ACTION -> {
request.data.map { property -> request.data.map { property ->
MessageData { MessageData {
name = property.name name = property.name
value = getProperty(name) value = device.getProperty(name)
} }
} }
} }
@ -113,13 +132,13 @@ suspend fun Device.respondMessage(
val propertyName: String = property.name val propertyName: String = property.name
val propertyValue = property.value val propertyValue = property.value
if (propertyValue == null) { if (propertyValue == null) {
invalidateProperty(propertyName) device.invalidateProperty(propertyName)
} else { } else {
setProperty(propertyName, propertyValue) device.setProperty(propertyName, propertyValue)
} }
MessageData { MessageData {
name = propertyName name = propertyName
value = getProperty(propertyName) value = device.getProperty(propertyName)
} }
} }
} }
@ -127,12 +146,12 @@ suspend fun Device.respondMessage(
request.data.map { payload -> request.data.map { payload ->
MessageData { MessageData {
name = payload.name name = payload.name
value = exec(payload.name, payload.value) value = device.execute(payload.name, payload.value)
} }
} }
} }
PROPERTY_LIST_ACTION -> { PROPERTY_LIST_ACTION -> {
propertyDescriptors.map { descriptor -> device.propertyDescriptors.map { descriptor ->
MessageData { MessageData {
name = descriptor.name name = descriptor.name
value = MetaItem.NodeItem(descriptor.config) value = MetaItem.NodeItem(descriptor.config)
@ -141,7 +160,7 @@ suspend fun Device.respondMessage(
} }
ACTION_LIST_ACTION -> { ACTION_LIST_ACTION -> {
actionDescriptors.map { descriptor -> device.actionDescriptors.map { descriptor ->
MessageData { MessageData {
name = descriptor.name name = descriptor.name
value = MetaItem.NodeItem(descriptor.config) value = MetaItem.NodeItem(descriptor.config)
@ -153,10 +172,18 @@ suspend fun Device.respondMessage(
error("Unrecognized action $action") error("Unrecognized action $action")
} }
} }
return DeviceMessage.ok { DeviceMessage.ok {
target = request.source target = request.source
source = deviceTarget
data = result data = result
} }
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}
}
}
}
} }
suspend fun Device.exec(name: String, meta: Meta?) = exec(name, meta?.let { MetaItem.NodeItem(it) }) suspend fun Device.execute(name: String, meta: Meta?): MetaItem<*>? = execute(name, meta?.let { MetaItem.NodeItem(it) })

View File

@ -13,7 +13,7 @@ import hep.dataforge.provider.Provider
* A hub that could locate multiple devices and redirect actions to them * A hub that could locate multiple devices and redirect actions to them
*/ */
interface DeviceHub : Provider { interface DeviceHub : Provider {
val devices: Map<Name, Device> val devices: Map<Name, Device>//TODO use token instead of Names
override val defaultTarget: String get() = Device.DEVICE_TARGET override val defaultTarget: String get() = Device.DEVICE_TARGET
@ -44,18 +44,24 @@ suspend fun DeviceHub.setProperty(deviceName: Name, propertyName: String, value:
this[deviceName].setProperty(propertyName, value) this[deviceName].setProperty(propertyName, value)
} }
suspend fun DeviceHub.exec(deviceName: Name, command: String, argument: MetaItem<*>?): MetaItem<*>? = suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: MetaItem<*>?): MetaItem<*>? =
this[deviceName].exec(command, argument) this[deviceName].execute(command, argument)
suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage { suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
val device = this[request.target?.toName() ?: Name.EMPTY] return try {
val targetName = request.target?.toName() ?: Name.EMPTY
return device.respondMessage(request) val device = this[targetName]
Device.respondMessage(device, targetName.toString(), request)
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}
}
} }
suspend fun DeviceHub.respond(request: Envelope): Envelope { suspend fun DeviceHub.respond(request: Envelope): Envelope {
val target = request.meta[DeviceMessage.TARGET_KEY].string val target = request.meta[DeviceMessage.TARGET_KEY].string ?: defaultTarget
val device = this[target?.toName() ?: Name.EMPTY] val device = this[target.toName()]
return device.respond(request) return Device.respond(device, target, request)
} }

View File

@ -8,5 +8,7 @@ import hep.dataforge.meta.MetaItem
*/ */
interface DeviceListener { interface DeviceListener {
fun propertyChanged(propertyName: String, value: MetaItem<*>?) fun propertyChanged(propertyName: String, value: MetaItem<*>?)
fun actionExecuted(action:String, argument: MetaItem<*>?, result: MetaItem<*>?)
//TODO add general message listener method //TODO add general message listener method
} }

View File

@ -54,7 +54,7 @@ abstract class DeviceBase : Device {
) )
} }
override suspend fun exec(action: String, argument: MetaItem<*>?): MetaItem<*>? = override suspend fun execute(action: String, argument: MetaItem<*>?): MetaItem<*>? =
(actions[action] ?: error("Request with name $action not defined")).invoke(argument) (actions[action] ?: error("Request with name $action not defined")).invoke(argument)

View File

@ -1,14 +1,13 @@
package hep.dataforge.control.controllers package hep.dataforge.control.controllers
import hep.dataforge.control.api.Consumer
import hep.dataforge.control.api.Device import hep.dataforge.control.api.Device
import hep.dataforge.control.api.DeviceListener import hep.dataforge.control.api.DeviceListener
import hep.dataforge.control.api.respondMessage
import hep.dataforge.control.controllers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION import hep.dataforge.control.controllers.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION
import hep.dataforge.io.Consumer
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.Responder
import hep.dataforge.io.SimpleEnvelope import hep.dataforge.io.SimpleEnvelope
import hep.dataforge.meta.* import hep.dataforge.meta.MetaItem
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.consumeAsFlow
@ -19,7 +18,7 @@ class DeviceController(
val device: Device, val device: Device,
val deviceTarget: String, val deviceTarget: String,
val scope: CoroutineScope = device.scope val scope: CoroutineScope = device.scope
) : Consumer, Responder, DeviceListener { ) : Responder, Consumer, DeviceListener {
init { init {
device.registerListener(this, this) device.registerListener(this, this)
@ -27,45 +26,12 @@ class DeviceController(
private val outputChannel = Channel<Envelope>(Channel.CONFLATED) private val outputChannel = Channel<Envelope>(Channel.CONFLATED)
override fun consume(message: Envelope) {
// Fire the respond procedure and forget about the result
scope.launch {
respond(message)
}
}
suspend fun respondMessage(message: DeviceMessage): DeviceMessage { suspend fun respondMessage(message: DeviceMessage): DeviceMessage {
return try { return Device.respondMessage(device, deviceTarget, message)
device.respondMessage(message).apply {
target = message.source
source = deviceTarget
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}
}
} }
override suspend fun respond(request: Envelope): Envelope { override suspend fun respond(request: Envelope): Envelope {
val target = request.meta["target"].string return Device.respond(device, deviceTarget, request)
return try {
if (request.data == null) {
respondMessage(DeviceMessage.wrap(request.meta)).wrap()
}else if(target != null && target != deviceTarget) {
error("Wrong target name $deviceTarget expected but ${target} found")
} else {
val response = device.respond(request)
return SimpleEnvelope(response.meta.edit {
"target" put request.meta["source"].string
"source" put deviceTarget
}, response.data)
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}.wrap()
}
} }
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) { override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
@ -87,6 +53,12 @@ class DeviceController(
fun output() = outputChannel.consumeAsFlow() fun output() = outputChannel.consumeAsFlow()
override fun consume(message: Envelope) {
// Fire the respond procedure and forget about the result
scope.launch {
respond(message)
}
}
companion object { companion object {

View File

@ -1,14 +1,12 @@
package hep.dataforge.control.controllers package hep.dataforge.control.controllers
import hep.dataforge.control.api.DeviceHub import hep.dataforge.control.api.*
import hep.dataforge.control.api.DeviceListener
import hep.dataforge.control.api.get
import hep.dataforge.control.api.respondMessage
import hep.dataforge.io.Consumer
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.Responder
import hep.dataforge.io.SimpleEnvelope import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.* import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.meta.wrap
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -59,40 +57,29 @@ class HubController(
} }
} }
suspend fun respondMessage(message: DeviceMessage): DeviceMessage { suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
return try {
val targetName = message.target?.toName() ?: Name.EMPTY val targetName = message.target?.toName() ?: Name.EMPTY
val device = hub[targetName] val device = hub[targetName]
device.respondMessage(message).apply { Device.respondMessage(device, targetName.toString(), message)
target = message.source
source = targetName.toString()
}
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.fail { DeviceMessage.fail {
comment = ex.message comment = ex.message
} }
} }
}
override suspend fun respond(request: Envelope): Envelope { override suspend fun respond(request: Envelope): Envelope = try {
val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY
return try {
val device = hub[targetName] val device = hub[targetName]
if (request.data == null) { if (request.data == null) {
respondMessage(DeviceMessage.wrap(request.meta)).wrap() Device.respondMessage(device, targetName.toString(), DeviceMessage.wrap(request.meta)).wrap()
} else { } else {
val response = device.respond(request) Device.respond(device, targetName.toString(), request)
return SimpleEnvelope(response.meta.edit {
DeviceMessage.TARGET_KEY put request.meta[DeviceMessage.SOURCE_KEY].string
DeviceMessage.SOURCE_KEY put targetName.toString()
}, response.data)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.fail { DeviceMessage.fail {
comment = ex.message comment = ex.message
}.wrap() }.wrap()
} }
}
override fun consume(message: Envelope) { override fun consume(message: Envelope) {
// Fire the respond procedure and forget about the result // Fire the respond procedure and forget about the result

View File

@ -185,8 +185,6 @@ fun Application.deviceModule(
} }
post("message") { post("message") {
val target: String by call.request.queryParameters
val device = manager[target]
val body = call.receiveText() val body = call.receiveText()
val json = Json.parseJson(body) as? JsonObject val json = Json.parseJson(body) as? JsonObject
?: throw IllegalArgumentException("The body is not a json object") ?: throw IllegalArgumentException("The body is not a json object")
@ -194,7 +192,7 @@ fun Application.deviceModule(
val request = DeviceMessage.wrap(meta) val request = DeviceMessage.wrap(meta)
val response = device.respondMessage(request) val response = manager.respondMessage(request)
call.respondMessage(response) call.respondMessage(response)
} }
@ -204,7 +202,6 @@ fun Application.deviceModule(
route("{property}") { route("{property}") {
get("get") { get("get") {
val target: String by call.parameters val target: String by call.parameters
val device = manager[target]
val property: String by call.parameters val property: String by call.parameters
val request = DeviceMessage { val request = DeviceMessage {
type = GET_PROPERTY_ACTION type = GET_PROPERTY_ACTION
@ -215,13 +212,11 @@ fun Application.deviceModule(
} }
} }
val response = device.respondMessage(request) val response = manager.respondMessage(request)
call.respondMessage(response) call.respondMessage(response)
} }
post("set") { post("set") {
val target: String by call.parameters val target: String by call.parameters
val device = manager[target]
val property: String by call.parameters val property: String by call.parameters
val body = call.receiveText() val body = call.receiveText()
val json = Json.parseJson(body) val json = Json.parseJson(body)
@ -236,7 +231,7 @@ fun Application.deviceModule(
} }
} }
val response = device.respondMessage(request) val response = manager.respondMessage(request)
call.respondMessage(response) call.respondMessage(response)
} }
} }

11
motors/build.gradle.kts Normal file
View File

@ -0,0 +1,11 @@
plugins {
id("scientifik.jvm")
id("scientifik.publish")
}
//TODO to be moved to a separate project
dependencies {
implementation(project(":dataforge-device-core"))
}

View File

@ -38,7 +38,8 @@ include(
":dataforge-device-serial", ":dataforge-device-serial",
":dataforge-device-server", ":dataforge-device-server",
":dataforge-device-client", ":dataforge-device-client",
":demo" ":demo",
":motors"
) )
//includeBuild("../dataforge-core") //includeBuild("../dataforge-core")