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,70 +88,102 @@ 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"
}
}
suspend fun Device.respondMessage( internal suspend fun respond(device: Device, deviceTarget: String, request: Envelope): Envelope {
request: DeviceMessage val target = request.meta["target"].string
): DeviceMessage { return try {
val result: List<MessageData> = when (val action = request.type) { if (request.data == null) {
GET_PROPERTY_ACTION -> { respondMessage(device, deviceTarget, DeviceMessage.wrap(request.meta)).wrap()
request.data.map { property -> } else if (target != null && target != deviceTarget) {
MessageData { error("Wrong target name $deviceTarget expected but $target found")
name = property.name
value = getProperty(name)
}
}
}
SET_PROPERTY_ACTION -> {
request.data.map { property ->
val propertyName: String = property.name
val propertyValue = property.value
if (propertyValue == null) {
invalidateProperty(propertyName)
} else { } else {
setProperty(propertyName, propertyValue) val response = device.respondWithData(request).apply {
} meta {
MessageData { "target" put request.meta["source"].string
name = propertyName "source" put deviceTarget
value = getProperty(propertyName) }
} }
} return response.build()
}
EXECUTE_ACTION -> {
request.data.map { payload ->
MessageData {
name = payload.name
value = exec(payload.name, payload.value)
}
}
}
PROPERTY_LIST_ACTION -> {
propertyDescriptors.map { descriptor ->
MessageData {
name = descriptor.name
value = MetaItem.NodeItem(descriptor.config)
} }
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}.wrap()
} }
} }
ACTION_LIST_ACTION -> { internal suspend fun respondMessage(
actionDescriptors.map { descriptor -> device: Device,
MessageData { deviceTarget: String,
name = descriptor.name request: DeviceMessage
value = MetaItem.NodeItem(descriptor.config) ): DeviceMessage {
return try {
val result: List<MessageData> = when (val action = request.type) {
GET_PROPERTY_ACTION -> {
request.data.map { property ->
MessageData {
name = property.name
value = device.getProperty(name)
}
}
}
SET_PROPERTY_ACTION -> {
request.data.map { property ->
val propertyName: String = property.name
val propertyValue = property.value
if (propertyValue == null) {
device.invalidateProperty(propertyName)
} else {
device.setProperty(propertyName, propertyValue)
}
MessageData {
name = propertyName
value = device.getProperty(propertyName)
}
}
}
EXECUTE_ACTION -> {
request.data.map { payload ->
MessageData {
name = payload.name
value = device.execute(payload.name, payload.value)
}
}
}
PROPERTY_LIST_ACTION -> {
device.propertyDescriptors.map { descriptor ->
MessageData {
name = descriptor.name
value = MetaItem.NodeItem(descriptor.config)
}
}
}
ACTION_LIST_ACTION -> {
device.actionDescriptors.map { descriptor ->
MessageData {
name = descriptor.name
value = MetaItem.NodeItem(descriptor.config)
}
}
}
else -> {
error("Unrecognized action $action")
}
}
DeviceMessage.ok {
target = request.source
source = deviceTarget
data = result
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
} }
} }
} }
else -> {
error("Unrecognized action $action")
}
}
return DeviceMessage.ok {
target = request.source
data = result
} }
} }
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,8 +53,14 @@ 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,39 +57,28 @@ 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(device, targetName.toString(), message)
device.respondMessage(message).apply { } catch (ex: Exception) {
target = message.source DeviceMessage.fail {
source = targetName.toString() comment = ex.message
}
} catch (ex: Exception) {
DeviceMessage.fail {
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) { Device.respondMessage(device, targetName.toString(), DeviceMessage.wrap(request.meta)).wrap()
respondMessage(DeviceMessage.wrap(request.meta)).wrap() } else {
} else { Device.respond(device, targetName.toString(), request)
val response = device.respond(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) {
DeviceMessage.fail {
comment = ex.message
}.wrap()
} }
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}.wrap()
} }
override fun consume(message: Envelope) { override fun consume(message: Envelope) {

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")