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 {
repositories {

View File

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

View File

@ -1,30 +1,26 @@
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.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.MessageData
import hep.dataforge.control.controllers.wrap
import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder
import hep.dataforge.io.SimpleEnvelope
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.wrap
import hep.dataforge.io.EnvelopeBuilder
import hep.dataforge.meta.*
import hep.dataforge.provider.Type
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.io.Binary
import kotlinx.io.Closeable
interface Consumer {
fun consume(message: Envelope): Unit
}
/**
* General interface describing a managed Device
*/
@Type(DEVICE_TARGET)
interface Device: Closeable, Responder {
interface Device: Closeable{
/**
* 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.
* 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() {
scope.cancel("The device is closed")
@ -93,70 +88,102 @@ interface Device: Closeable, Responder {
const val EXECUTE_ACTION = "execute"
const val PROPERTY_LIST_ACTION = "propertyList"
const val ACTION_LIST_ACTION = "actionList"
}
}
suspend fun Device.respondMessage(
request: DeviceMessage
): DeviceMessage {
val result: List<MessageData> = when (val action = request.type) {
GET_PROPERTY_ACTION -> {
request.data.map { property ->
MessageData {
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)
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 {
setProperty(propertyName, propertyValue)
}
MessageData {
name = propertyName
value = getProperty(propertyName)
}
}
}
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)
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()
}
}
ACTION_LIST_ACTION -> {
actionDescriptors.map { descriptor ->
MessageData {
name = descriptor.name
value = MetaItem.NodeItem(descriptor.config)
internal suspend fun respondMessage(
device: Device,
deviceTarget: String,
request: DeviceMessage
): 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
*/
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
@ -44,18 +44,24 @@ suspend fun DeviceHub.setProperty(deviceName: Name, propertyName: String, value:
this[deviceName].setProperty(propertyName, value)
}
suspend fun DeviceHub.exec(deviceName: Name, command: String, argument: MetaItem<*>?): MetaItem<*>? =
this[deviceName].exec(command, argument)
suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: MetaItem<*>?): MetaItem<*>? =
this[deviceName].execute(command, argument)
suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
val device = this[request.target?.toName() ?: Name.EMPTY]
return device.respondMessage(request)
return try {
val targetName = request.target?.toName() ?: Name.EMPTY
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 {
val target = request.meta[DeviceMessage.TARGET_KEY].string
val device = this[target?.toName() ?: Name.EMPTY]
val target = request.meta[DeviceMessage.TARGET_KEY].string ?: defaultTarget
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 {
fun propertyChanged(propertyName: String, value: MetaItem<*>?)
fun actionExecuted(action:String, argument: MetaItem<*>?, result: MetaItem<*>?)
//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)

View File

@ -1,14 +1,13 @@
package hep.dataforge.control.controllers
import hep.dataforge.control.api.Consumer
import hep.dataforge.control.api.Device
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.io.Consumer
import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder
import hep.dataforge.io.SimpleEnvelope
import hep.dataforge.meta.*
import hep.dataforge.meta.MetaItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
@ -19,7 +18,7 @@ class DeviceController(
val device: Device,
val deviceTarget: String,
val scope: CoroutineScope = device.scope
) : Consumer, Responder, DeviceListener {
) : Responder, Consumer, DeviceListener {
init {
device.registerListener(this, this)
@ -27,45 +26,12 @@ class DeviceController(
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 {
return try {
device.respondMessage(message).apply {
target = message.source
source = deviceTarget
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}
}
return Device.respondMessage(device, deviceTarget, message)
}
override suspend fun respond(request: Envelope): Envelope {
val target = request.meta["target"].string
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()
}
return Device.respond(device, deviceTarget, request)
}
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
@ -87,8 +53,14 @@ class DeviceController(
fun output() = outputChannel.consumeAsFlow()
override fun consume(message: Envelope) {
// Fire the respond procedure and forget about the result
scope.launch {
respond(message)
}
}
companion object {
}
}
}

View File

@ -1,14 +1,12 @@
package hep.dataforge.control.controllers
import hep.dataforge.control.api.DeviceHub
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.control.api.*
import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder
import hep.dataforge.io.SimpleEnvelope
import hep.dataforge.meta.*
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.meta.wrap
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import kotlinx.coroutines.CoroutineScope
@ -59,39 +57,28 @@ class HubController(
}
}
suspend fun respondMessage(message: DeviceMessage): DeviceMessage {
return try {
val targetName = message.target?.toName() ?: Name.EMPTY
val device = hub[targetName]
device.respondMessage(message).apply {
target = message.source
source = targetName.toString()
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}
suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
val targetName = message.target?.toName() ?: Name.EMPTY
val device = hub[targetName]
Device.respondMessage(device, targetName.toString(), 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
return try {
val device = hub[targetName]
if (request.data == null) {
respondMessage(DeviceMessage.wrap(request.meta)).wrap()
} else {
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()
val device = hub[targetName]
if (request.data == null) {
Device.respondMessage(device, targetName.toString(), DeviceMessage.wrap(request.meta)).wrap()
} else {
Device.respond(device, targetName.toString(), request)
}
} catch (ex: Exception) {
DeviceMessage.fail {
comment = ex.message
}.wrap()
}
override fun consume(message: Envelope) {

View File

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

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-server",
":dataforge-device-client",
":demo"
":demo",
":motors"
)
//includeBuild("../dataforge-core")