diff --git a/build.gradle.kts b/build.gradle.kts index 01e4311..fd641b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,4 @@ -val dataforgeVersion by extra("0.1.9-dev") - +val dataforgeVersion by extra("0.1.8") allprojects { repositories { diff --git a/dataforge-device-core/build.gradle.kts b/dataforge-device-core/build.gradle.kts index 5d1a507..c9465bc 100644 --- a/dataforge-device-core/build.gradle.kts +++ b/dataforge-device-core/build.gradle.kts @@ -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{} } } \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt index 7648704..27e3974 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt @@ -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 = 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 = 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) }) \ No newline at end of file +suspend fun Device.execute(name: String, meta: Meta?): MetaItem<*>? = execute(name, meta?.let { MetaItem.NodeItem(it) }) \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt index 7704d9e..cc0f147 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceHub.kt @@ -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 + val devices: Map//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) } \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt index 4082483..bc04397 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt @@ -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 } \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt index a92a131..0000bfc 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt @@ -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) diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt index dfe16c2..d545604 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt @@ -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(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 { } -} \ No newline at end of file +} diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/HubController.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/HubController.kt index f44f2ea..ae3065e 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/HubController.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/HubController.kt @@ -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) { diff --git a/dataforge-device-server/src/main/kotlin/hep/dataforge/control/server/deviceWebServer.kt b/dataforge-device-server/src/main/kotlin/hep/dataforge/control/server/deviceWebServer.kt index fcd831b..0fe32df 100644 --- a/dataforge-device-server/src/main/kotlin/hep/dataforge/control/server/deviceWebServer.kt +++ b/dataforge-device-server/src/main/kotlin/hep/dataforge/control/server/deviceWebServer.kt @@ -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) } } diff --git a/motors/build.gradle.kts b/motors/build.gradle.kts new file mode 100644 index 0000000..1599ffb --- /dev/null +++ b/motors/build.gradle.kts @@ -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")) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 993fdb1..c0294be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,7 +38,8 @@ include( ":dataforge-device-serial", ":dataforge-device-server", ":dataforge-device-client", - ":demo" + ":demo", + ":motors" ) //includeBuild("../dataforge-core")