Compare commits
No commits in common. "1619fdadf23e033b049d7ad3b724f8acffa012bb" and "290010fc8c2b71f789f8aa990080b8502b7325d8" have entirely different histories.
1619fdadf2
...
290010fc8c
@ -1,3 +1,4 @@
|
|||||||
|
import space.kscience.gradle.isInDevelopment
|
||||||
import space.kscience.gradle.useApache2Licence
|
import space.kscience.gradle.useApache2Licence
|
||||||
import space.kscience.gradle.useSPCTeam
|
import space.kscience.gradle.useSPCTeam
|
||||||
|
|
||||||
@ -13,18 +14,25 @@ val xodusVersion by extra("2.0.1")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.3.0-dev-1"
|
version = "0.2.2-dev-2"
|
||||||
repositories{
|
repositories{
|
||||||
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ksciencePublish {
|
ksciencePublish {
|
||||||
pom("https://github.com/SciProgCentre/controls-kt") {
|
pom("https://github.com/SciProgCentre/controls.kt") {
|
||||||
useApache2Licence()
|
useApache2Licence()
|
||||||
useSPCTeam()
|
useSPCTeam()
|
||||||
}
|
}
|
||||||
repository("spc","https://maven.sciprog.center/kscience")
|
github("controls.kt", "SciProgCentre")
|
||||||
|
space(
|
||||||
|
if (isInDevelopment) {
|
||||||
|
"https://maven.pkg.jetbrains.space/spc/p/sci/dev"
|
||||||
|
} else {
|
||||||
|
"https://maven.pkg.jetbrains.space/spc/p/sci/maven"
|
||||||
|
}
|
||||||
|
)
|
||||||
sonatype("https://oss.sonatype.org")
|
sonatype("https://oss.sonatype.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("space.kscience.gradle.mpp")
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
description = """
|
|
||||||
A low-code constructor foe composite devices simulation
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
kscience{
|
|
||||||
jvm()
|
|
||||||
js()
|
|
||||||
dependencies {
|
|
||||||
api(projects.controlsCore)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readme{
|
|
||||||
maturity = space.kscience.gradle.Maturity.PROTOTYPE
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package center.sciprog.controls.devices.misc
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
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.doubleProperty
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single axis drive
|
|
||||||
*/
|
|
||||||
public interface Drive : Device {
|
|
||||||
/**
|
|
||||||
* Get or set target value
|
|
||||||
*/
|
|
||||||
public var target: Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current position value
|
|
||||||
*/
|
|
||||||
public val position: Double
|
|
||||||
|
|
||||||
public companion object : DeviceSpec<Drive>() {
|
|
||||||
public val target: DevicePropertySpec<Drive, Double> by property(MetaConverter.double, Drive::target)
|
|
||||||
|
|
||||||
public val position: DevicePropertySpec<Drive, Double> by doubleProperty { position }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Virtual [Drive] with speed limit
|
|
||||||
*/
|
|
||||||
public class VirtualDrive(
|
|
||||||
context: Context,
|
|
||||||
value: Double,
|
|
||||||
private val speed: Double,
|
|
||||||
) : DeviceBySpec<Drive>(Drive, context), Drive {
|
|
||||||
|
|
||||||
private var moveJob: Job? = null
|
|
||||||
|
|
||||||
override var position: Double = value
|
|
||||||
private set
|
|
||||||
|
|
||||||
override var target: Double = value
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package center.sciprog.controls.devices.misc
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Virtual [LimitSwitch]
|
|
||||||
*/
|
|
||||||
public class VirtualLimitSwitch(
|
|
||||||
context: Context,
|
|
||||||
private val lockedFunction: () -> Boolean,
|
|
||||||
) : DeviceBySpec<LimitSwitch>(LimitSwitch, context), LimitSwitch {
|
|
||||||
override val locked: Boolean get() = lockedFunction()
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
package center.sciprog.controls.devices.misc
|
|
||||||
|
|
||||||
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.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.spec.DeviceBySpec
|
|
||||||
import space.kscience.controls.spec.DeviceSpec
|
|
||||||
import space.kscience.controls.spec.doubleProperty
|
|
||||||
import space.kscience.dataforge.context.Context
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.double
|
|
||||||
import space.kscience.dataforge.meta.get
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
|
|
||||||
|
|
||||||
interface PidRegulator : Device {
|
|
||||||
/**
|
|
||||||
* Proportional coefficient
|
|
||||||
*/
|
|
||||||
val kp: Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integral coefficient
|
|
||||||
*/
|
|
||||||
val ki: Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Differential coefficient
|
|
||||||
*/
|
|
||||||
val kd: Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The target value for PID
|
|
||||||
*/
|
|
||||||
var target: Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read current value
|
|
||||||
*/
|
|
||||||
suspend fun read(): Double
|
|
||||||
|
|
||||||
companion object : DeviceSpec<PidRegulator>() {
|
|
||||||
val target by property(MetaConverter.double, PidRegulator::target)
|
|
||||||
val value by doubleProperty { read() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class VirtualPid(
|
|
||||||
context: Context,
|
|
||||||
override val kp: Double,
|
|
||||||
override val ki: Double,
|
|
||||||
override val kd: Double,
|
|
||||||
val mass: Double,
|
|
||||||
override var target: Double = 0.0,
|
|
||||||
private val dt: Duration = 0.5.milliseconds,
|
|
||||||
private val clock: Clock = Clock.System,
|
|
||||||
) : DeviceBySpec<PidRegulator>(PidRegulator, context), PidRegulator {
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
|
|
||||||
private var lastTime: Instant = clock.now()
|
|
||||||
private var lastValue: Double = target
|
|
||||||
|
|
||||||
private var value: Double = target
|
|
||||||
private var velocity: Double = 0.0
|
|
||||||
private var acceleration: Double = 0.0
|
|
||||||
private var integral: Double = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
private var updateJob: Job? = null
|
|
||||||
|
|
||||||
override suspend fun onStart() {
|
|
||||||
updateJob = launch {
|
|
||||||
while (isActive) {
|
|
||||||
delay(dt)
|
|
||||||
mutex.withLock {
|
|
||||||
val realTime = clock.now()
|
|
||||||
val delta = target - value
|
|
||||||
val dtSeconds = (realTime - lastTime).toDouble(DurationUnit.SECONDS)
|
|
||||||
integral += delta * dtSeconds
|
|
||||||
val derivative = (value - lastValue) / dtSeconds
|
|
||||||
|
|
||||||
//set last time and value to new values
|
|
||||||
lastTime = realTime
|
|
||||||
lastValue = value
|
|
||||||
|
|
||||||
// compute new value based on velocity and acceleration from the previous step
|
|
||||||
value += velocity * dtSeconds + acceleration * dtSeconds.pow(2) / 2
|
|
||||||
|
|
||||||
// compute new velocity based on acceleration on the previous step
|
|
||||||
velocity += acceleration * dtSeconds
|
|
||||||
|
|
||||||
//compute force for the next step based on current values
|
|
||||||
acceleration = (kp * delta + ki * integral + kd * derivative) / mass
|
|
||||||
|
|
||||||
|
|
||||||
check(value.isFinite() && velocity.isFinite()) {
|
|
||||||
"Value $value is not finite"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
updateJob?.cancel()
|
|
||||||
super<PidRegulator>.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun read(): Double = value
|
|
||||||
|
|
||||||
suspend fun readVelocity(): Double = velocity
|
|
||||||
|
|
||||||
suspend fun readAcceleration(): Double = acceleration
|
|
||||||
|
|
||||||
suspend fun write(newTarget: Double) = mutex.withLock {
|
|
||||||
require(newTarget.isFinite()) { "Value $newTarget is not valid" }
|
|
||||||
target = newTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : Factory<Device> {
|
|
||||||
override fun build(context: Context, meta: Meta) = VirtualPid(
|
|
||||||
context,
|
|
||||||
meta["kp"].double ?: error("Kp is not defined"),
|
|
||||||
meta["ki"].double ?: error("Ki is not defined"),
|
|
||||||
meta["kd"].double ?: error("Kd is not defined"),
|
|
||||||
meta["m"].double ?: error("Mass is not defined"),
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,19 +20,8 @@ import space.kscience.dataforge.names.Name
|
|||||||
* A lifecycle state of a device
|
* A lifecycle state of a device
|
||||||
*/
|
*/
|
||||||
public enum class DeviceLifecycleState{
|
public enum class DeviceLifecycleState{
|
||||||
/**
|
|
||||||
* Device is initializing
|
|
||||||
*/
|
|
||||||
INIT,
|
INIT,
|
||||||
|
|
||||||
/**
|
|
||||||
* The Device is initialized and running
|
|
||||||
*/
|
|
||||||
OPEN,
|
OPEN,
|
||||||
|
|
||||||
/**
|
|
||||||
* The Device is closed
|
|
||||||
*/
|
|
||||||
CLOSED
|
CLOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,14 +31,13 @@ public enum class DeviceLifecycleState{
|
|||||||
* When canceled, cancels all running processes.
|
* When canceled, cancels all running processes.
|
||||||
*/
|
*/
|
||||||
@Type(DEVICE_TARGET)
|
@Type(DEVICE_TARGET)
|
||||||
public interface Device : ContextAware, CoroutineScope {
|
public interface Device : AutoCloseable, ContextAware, CoroutineScope {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial configuration meta for the device
|
* Initial configuration meta for the device
|
||||||
*/
|
*/
|
||||||
public val meta: Meta get() = Meta.EMPTY
|
public val meta: Meta get() = Meta.EMPTY
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of supported property descriptors
|
* List of supported property descriptors
|
||||||
*/
|
*/
|
||||||
@ -99,12 +87,12 @@ public interface Device : ContextAware, CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* Initialize the device. This function suspends until the device is finished initialization
|
* Initialize the device. This function suspends until the device is finished initialization
|
||||||
*/
|
*/
|
||||||
public suspend fun start(): Unit = Unit
|
public suspend fun open(): Unit = Unit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close and terminate the device. This function does not wait for the device to be closed.
|
* Close and terminate the device. This function does not wait for the device to be closed.
|
||||||
*/
|
*/
|
||||||
public fun stop() {
|
override fun close() {
|
||||||
logger.info { "Device $this is closed" }
|
logger.info { "Device $this is closed" }
|
||||||
cancel("The device is closed")
|
cancel("The device is closed")
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ public sealed class DeviceMessage {
|
|||||||
public abstract val time: Instant?
|
public abstract val time: Instant?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the source device name for composition. If the original name is null, the resulting name is also null.
|
* Update the source device name for composition. If the original name is null, resulting name is also null.
|
||||||
*/
|
*/
|
||||||
public abstract fun changeSource(block: (Name) -> Name): DeviceMessage
|
public abstract fun changeSource(block: (Name) -> Name): DeviceMessage
|
||||||
|
|
||||||
@ -203,12 +203,12 @@ public data class EmptyDeviceMessage(
|
|||||||
public data class DeviceLogMessage(
|
public data class DeviceLogMessage(
|
||||||
val message: String,
|
val message: String,
|
||||||
val data: Meta? = null,
|
val data: Meta? = null,
|
||||||
override val sourceDevice: Name = Name.EMPTY,
|
override val sourceDevice: Name? = null,
|
||||||
override val targetDevice: Name? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
@EncodeDefault override val time: Instant? = Clock.System.now(),
|
@EncodeDefault override val time: Instant? = Clock.System.now(),
|
||||||
) : DeviceMessage() {
|
) : DeviceMessage() {
|
||||||
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice))
|
override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,7 +220,7 @@ public data class DeviceErrorMessage(
|
|||||||
public val errorMessage: String?,
|
public val errorMessage: String?,
|
||||||
public val errorType: String? = null,
|
public val errorType: String? = null,
|
||||||
public val errorStackTrace: String? = null,
|
public val errorStackTrace: String? = null,
|
||||||
override val sourceDevice: Name = Name.EMPTY,
|
override val sourceDevice: Name,
|
||||||
override val targetDevice: Name? = null,
|
override val targetDevice: Name? = null,
|
||||||
override val comment: String? = null,
|
override val comment: String? = null,
|
||||||
@EncodeDefault override val time: Instant? = Clock.System.now(),
|
@EncodeDefault override val time: Instant? = Clock.System.now(),
|
||||||
|
@ -12,10 +12,10 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
|
|||||||
@Serializable
|
@Serializable
|
||||||
public class PropertyDescriptor(
|
public class PropertyDescriptor(
|
||||||
public val name: String,
|
public val name: String,
|
||||||
public var description: String? = null,
|
public var info: String? = null,
|
||||||
public var metaDescriptor: MetaDescriptor = MetaDescriptor(),
|
public var metaDescriptor: MetaDescriptor = MetaDescriptor(),
|
||||||
public var readable: Boolean = true,
|
public var readable: Boolean = true,
|
||||||
public var mutable: Boolean = false
|
public var writable: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.()->Unit){
|
public fun PropertyDescriptor.metaDescriptor(block: MetaDescriptorBuilder.()->Unit){
|
||||||
|
@ -40,7 +40,7 @@ public class DeviceManager : AbstractPlugin(), DeviceHub {
|
|||||||
public fun <D : Device> DeviceManager.install(name: String, device: D): D {
|
public fun <D : Device> DeviceManager.install(name: String, device: D): D {
|
||||||
registerDevice(NameToken(name), device)
|
registerDevice(NameToken(name), device)
|
||||||
device.launch {
|
device.launch {
|
||||||
device.start()
|
device.open()
|
||||||
}
|
}
|
||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import io.ktor.utils.io.core.readBytes
|
|||||||
import io.ktor.utils.io.core.reset
|
import io.ktor.utils.io.core.reset
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
|
||||||
import kotlinx.coroutines.flow.transform
|
import kotlinx.coroutines.flow.transform
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,10 +16,6 @@ public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray>
|
|||||||
val output = BytePacketBuilder()
|
val output = BytePacketBuilder()
|
||||||
var matcherPosition = 0
|
var matcherPosition = 0
|
||||||
|
|
||||||
onCompletion {
|
|
||||||
output.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform { chunk ->
|
return transform { chunk ->
|
||||||
chunk.forEach { byte ->
|
chunk.forEach { byte ->
|
||||||
output.writeByte(byte)
|
output.writeByte(byte)
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package space.kscience.controls.spec
|
package space.kscience.controls.spec
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.newCoroutineContext
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import space.kscience.controls.api.*
|
import space.kscience.controls.api.*
|
||||||
@ -66,28 +69,8 @@ public abstract class DeviceBase<D : Device>(
|
|||||||
override val actionDescriptors: Collection<ActionDescriptor>
|
override val actionDescriptors: Collection<ActionDescriptor>
|
||||||
get() = actions.values.map { it.descriptor }
|
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 by lazy {
|
override val coroutineContext: CoroutineContext by lazy {
|
||||||
context.newCoroutineContext(
|
context.newCoroutineContext(SupervisorJob(context.coroutineContext[Job]) + CoroutineName("Device $this"))
|
||||||
SupervisorJob(context.coroutineContext[Job]) +
|
|
||||||
CoroutineName("Device $this") +
|
|
||||||
CoroutineExceptionHandler { _, throwable ->
|
|
||||||
launch {
|
|
||||||
sharedMessageFlow.emit(
|
|
||||||
DeviceErrorMessage(
|
|
||||||
errorMessage = throwable.message,
|
|
||||||
errorType = throwable::class.simpleName,
|
|
||||||
errorStackTrace = throwable.stackTraceToString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -96,6 +79,11 @@ public abstract class DeviceBase<D : Device>(
|
|||||||
*/
|
*/
|
||||||
private val logicalState: HashMap<String, Meta?> = HashMap()
|
private val logicalState: HashMap<String, Meta?> = HashMap()
|
||||||
|
|
||||||
|
private val sharedMessageFlow: MutableSharedFlow<DeviceMessage> = MutableSharedFlow(
|
||||||
|
replay = meta["message.buffer"].int ?: 1000,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||||
|
)
|
||||||
|
|
||||||
public override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
public override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -192,30 +180,18 @@ public abstract class DeviceBase<D : Device>(
|
|||||||
override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.INIT
|
override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.INIT
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
protected open suspend fun onStart() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
final override suspend fun start() {
|
override suspend fun open() {
|
||||||
super.start()
|
super.open()
|
||||||
lifecycleState = DeviceLifecycleState.INIT
|
|
||||||
onStart()
|
|
||||||
lifecycleState = DeviceLifecycleState.OPEN
|
lifecycleState = DeviceLifecycleState.OPEN
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onStop() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
final override fun stop() {
|
override fun close() {
|
||||||
onStop()
|
|
||||||
lifecycleState = DeviceLifecycleState.CLOSED
|
lifecycleState = DeviceLifecycleState.CLOSED
|
||||||
super.stop()
|
super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract override fun toString(): String
|
abstract override fun toString(): String
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,15 @@ public open class DeviceBySpec<D : Device>(
|
|||||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||||
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
||||||
|
|
||||||
override suspend fun onStart(): Unit = with(spec) {
|
override suspend fun open(): Unit = with(spec) {
|
||||||
|
super.open()
|
||||||
self.onOpen()
|
self.onOpen()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop(): Unit = with(spec){
|
override fun close(): Unit = with(spec) {
|
||||||
self.onClose()
|
self.onClose()
|
||||||
|
super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String = "Device(spec=$spec)"
|
override fun toString(): String = "Device(spec=$spec)"
|
||||||
}
|
}
|
@ -20,7 +20,7 @@ public annotation class InternalDeviceAPI
|
|||||||
/**
|
/**
|
||||||
* Specification for a device read-only property
|
* Specification for a device read-only property
|
||||||
*/
|
*/
|
||||||
public interface DevicePropertySpec<in D, T> {
|
public interface DevicePropertySpec<in D : Device, T> {
|
||||||
/**
|
/**
|
||||||
* Property descriptor
|
* Property descriptor
|
||||||
*/
|
*/
|
||||||
@ -53,7 +53,7 @@ public interface WritableDevicePropertySpec<in D : Device, T> : DevicePropertySp
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DeviceActionSpec<in D, I, O> {
|
public interface DeviceActionSpec<in D : Device, I, O> {
|
||||||
/**
|
/**
|
||||||
* Action descriptor
|
* Action descriptor
|
||||||
*/
|
*/
|
||||||
|
@ -53,7 +53,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||||
//TODO add type from converter
|
//TODO add type from converter
|
||||||
mutable = true
|
writable = true
|
||||||
}.apply(descriptorBuilder)
|
}.apply(descriptorBuilder)
|
||||||
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
@ -78,7 +78,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
|
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
|
||||||
//TODO add the type from converter
|
//TODO add the type from converter
|
||||||
mutable = true
|
writable = true
|
||||||
}.apply(descriptorBuilder)
|
}.apply(descriptorBuilder)
|
||||||
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
@ -127,7 +127,7 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
PropertyDelegateProvider { _: DeviceSpec<D>, property: KProperty<*> ->
|
||||||
val propertyName = name ?: property.name
|
val propertyName = name ?: property.name
|
||||||
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
val deviceProperty = object : WritableDevicePropertySpec<D, T> {
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName, mutable = true)
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName, writable = true)
|
||||||
.apply(descriptorBuilder)
|
.apply(descriptorBuilder)
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
|
||||||
@ -224,7 +224,7 @@ public fun <T, D : DeviceBase<D>> DeviceSpec<D>.logicalProperty(
|
|||||||
val propertyName = name ?: property.name
|
val propertyName = name ?: property.name
|
||||||
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
|
override val descriptor: PropertyDescriptor = PropertyDescriptor(propertyName).apply {
|
||||||
//TODO add type from converter
|
//TODO add type from converter
|
||||||
mutable = true
|
writable = true
|
||||||
}.apply(descriptorBuilder)
|
}.apply(descriptorBuilder)
|
||||||
|
|
||||||
override val converter: MetaConverter<T> = converter
|
override val converter: MetaConverter<T> = converter
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package space.kscience.controls.spec
|
|
||||||
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.api.DeviceHub
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
|
||||||
import space.kscience.dataforge.context.Factory
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.get
|
|
||||||
import space.kscience.dataforge.names.NameToken
|
|
||||||
import kotlin.collections.Map
|
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlin.collections.mapValues
|
|
||||||
import kotlin.collections.mutableMapOf
|
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
|
|
||||||
public class DeviceTree(
|
|
||||||
public val deviceManager: DeviceManager,
|
|
||||||
public val meta: Meta,
|
|
||||||
builder: Builder,
|
|
||||||
) : DeviceHub {
|
|
||||||
public class Builder(public val manager: DeviceManager) {
|
|
||||||
internal val childrenFactories = mutableMapOf<NameToken, Factory<Device>>()
|
|
||||||
|
|
||||||
public fun <D : Device> device(name: String, factory: Factory<Device>) {
|
|
||||||
childrenFactories[NameToken.parse(name)] = factory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val devices: Map<NameToken, Device> = builder.childrenFactories.mapValues { (token, factory) ->
|
|
||||||
val devicesMeta = meta["devices"]
|
|
||||||
factory.build(deviceManager.context, devicesMeta?.get(token) ?: Meta.EMPTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ description = """
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.controlsCore)
|
api(projects.controlsCore)
|
||||||
api("com.ghgande:j2mod:3.2.0")
|
api("com.ghgande:j2mod:3.1.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
readme{
|
readme{
|
||||||
|
@ -237,7 +237,7 @@ public fun <D : Device> D.bindProcessImage(
|
|||||||
image.setLocked(true)
|
image.setLocked(true)
|
||||||
if (openOnBind) {
|
if (openOnBind) {
|
||||||
launch {
|
launch {
|
||||||
start()
|
open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return image
|
return image
|
||||||
|
@ -20,14 +20,16 @@ public open class ModbusDeviceBySpec<D: Device>(
|
|||||||
private val disposeMasterOnClose: Boolean = true,
|
private val disposeMasterOnClose: Boolean = true,
|
||||||
meta: Meta = Meta.EMPTY,
|
meta: Meta = Meta.EMPTY,
|
||||||
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta){
|
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta){
|
||||||
override suspend fun onStart() {
|
override suspend fun open() {
|
||||||
master.connect()
|
master.connect()
|
||||||
|
super<DeviceBySpec>.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun close() {
|
||||||
if(disposeMasterOnClose){
|
if(disposeMasterOnClose){
|
||||||
master.disconnect()
|
master.disconnect()
|
||||||
}
|
}
|
||||||
|
super<ModbusDevice>.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
package space.kscience.controls.modbus
|
package space.kscience.controls.modbus
|
||||||
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import space.kscience.dataforge.io.IOFormat
|
import space.kscience.dataforge.io.IOFormat
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modbus registry key
|
|
||||||
*/
|
|
||||||
public sealed class ModbusRegistryKey {
|
public sealed class ModbusRegistryKey {
|
||||||
public abstract val address: Int
|
public abstract val address: Int
|
||||||
public open val count: Int = 1
|
public open val count: Int = 1
|
||||||
@ -32,9 +25,6 @@ public sealed class ModbusRegistryKey {
|
|||||||
override fun toString(): String = "InputRegister(address=$address)"
|
override fun toString(): String = "InputRegister(address=$address)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A range of read-only register encoding a single value
|
|
||||||
*/
|
|
||||||
public class InputRange<T>(
|
public class InputRange<T>(
|
||||||
address: Int,
|
address: Int,
|
||||||
override val count: Int,
|
override val count: Int,
|
||||||
@ -46,16 +36,10 @@ public sealed class ModbusRegistryKey {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A single read-write register
|
|
||||||
*/
|
|
||||||
public open class HoldingRegister(override val address: Int) : ModbusRegistryKey() {
|
public open class HoldingRegister(override val address: Int) : ModbusRegistryKey() {
|
||||||
override fun toString(): String = "HoldingRegister(address=$address)"
|
override fun toString(): String = "HoldingRegister(address=$address)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A range of read-write registers encoding a single value
|
|
||||||
*/
|
|
||||||
public class HoldingRange<T>(
|
public class HoldingRange<T>(
|
||||||
address: Int,
|
address: Int,
|
||||||
override val count: Int,
|
override val count: Int,
|
||||||
@ -68,9 +52,6 @@ public sealed class ModbusRegistryKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A base class for modbus registers
|
|
||||||
*/
|
|
||||||
public abstract class ModbusRegistryMap {
|
public abstract class ModbusRegistryMap {
|
||||||
|
|
||||||
private val _entries: MutableMap<ModbusRegistryKey, String> = mutableMapOf<ModbusRegistryKey, String>()
|
private val _entries: MutableMap<ModbusRegistryKey, String> = mutableMapOf<ModbusRegistryKey, String>()
|
||||||
@ -82,56 +63,36 @@ public abstract class ModbusRegistryMap {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.Coil] key and return it
|
|
||||||
*/
|
|
||||||
protected fun coil(address: Int, description: String = ""): ModbusRegistryKey.Coil =
|
protected fun coil(address: Int, description: String = ""): ModbusRegistryKey.Coil =
|
||||||
register(ModbusRegistryKey.Coil(address), description)
|
register(ModbusRegistryKey.Coil(address), description)
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.DiscreteInput] key and return it
|
|
||||||
*/
|
|
||||||
protected fun discrete(address: Int, description: String = ""): ModbusRegistryKey.DiscreteInput =
|
protected fun discrete(address: Int, description: String = ""): ModbusRegistryKey.DiscreteInput =
|
||||||
register(ModbusRegistryKey.DiscreteInput(address), description)
|
register(ModbusRegistryKey.DiscreteInput(address), description)
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.InputRegister] key and return it
|
|
||||||
*/
|
|
||||||
protected fun input(address: Int, description: String = ""): ModbusRegistryKey.InputRegister =
|
protected fun input(address: Int, description: String = ""): ModbusRegistryKey.InputRegister =
|
||||||
register(ModbusRegistryKey.InputRegister(address), description)
|
register(ModbusRegistryKey.InputRegister(address), description)
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.InputRange] key and return it
|
|
||||||
*/
|
|
||||||
protected fun <T> input(
|
protected fun <T> input(
|
||||||
address: Int,
|
address: Int,
|
||||||
count: Int,
|
count: Int,
|
||||||
reader: IOFormat<T>,
|
reader: IOFormat<T>,
|
||||||
description: String = "",
|
description: String = "",
|
||||||
): ModbusRegistryKey.InputRange<T> = register(ModbusRegistryKey.InputRange(address, count, reader), description)
|
): ModbusRegistryKey.InputRange<T> =
|
||||||
|
register(ModbusRegistryKey.InputRange(address, count, reader), description)
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.HoldingRegister] key and return it
|
|
||||||
*/
|
|
||||||
protected fun register(address: Int, description: String = ""): ModbusRegistryKey.HoldingRegister =
|
protected fun register(address: Int, description: String = ""): ModbusRegistryKey.HoldingRegister =
|
||||||
register(ModbusRegistryKey.HoldingRegister(address), description)
|
register(ModbusRegistryKey.HoldingRegister(address), description)
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a [ModbusRegistryKey.HoldingRange] key and return it
|
|
||||||
*/
|
|
||||||
protected fun <T> register(
|
protected fun <T> register(
|
||||||
address: Int,
|
address: Int,
|
||||||
count: Int,
|
count: Int,
|
||||||
format: IOFormat<T>,
|
format: IOFormat<T>,
|
||||||
description: String = "",
|
description: String = "",
|
||||||
): ModbusRegistryKey.HoldingRange<T> = register(ModbusRegistryKey.HoldingRange(address, count, format), description)
|
): ModbusRegistryKey.HoldingRange<T> =
|
||||||
|
register(ModbusRegistryKey.HoldingRange(address, count, format), description)
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the register map. Throw an error if the map is invalid
|
|
||||||
*/
|
|
||||||
public fun validate(map: ModbusRegistryMap) {
|
public fun validate(map: ModbusRegistryMap) {
|
||||||
var lastCoil: ModbusRegistryKey.Coil? = null
|
var lastCoil: ModbusRegistryKey.Coil? = null
|
||||||
var lastDiscreteInput: ModbusRegistryKey.DiscreteInput? = null
|
var lastDiscreteInput: ModbusRegistryKey.DiscreteInput? = null
|
||||||
@ -166,62 +127,36 @@ public abstract class ModbusRegistryMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private val ModbusRegistryKey.sectionNumber
|
||||||
}
|
get() = when (this) {
|
||||||
|
is ModbusRegistryKey.Coil -> 1
|
||||||
private val ModbusRegistryKey.sectionNumber
|
is ModbusRegistryKey.DiscreteInput -> 2
|
||||||
get() = when (this) {
|
is ModbusRegistryKey.HoldingRegister -> 4
|
||||||
is ModbusRegistryKey.Coil -> 1
|
is ModbusRegistryKey.InputRegister -> 3
|
||||||
is ModbusRegistryKey.DiscreteInput -> 2
|
|
||||||
is ModbusRegistryKey.HoldingRegister -> 4
|
|
||||||
is ModbusRegistryKey.InputRegister -> 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusRegistryMap.print(to: Appendable = System.out) {
|
|
||||||
ModbusRegistryMap.validate(this)
|
|
||||||
entries.entries
|
|
||||||
.sortedWith(
|
|
||||||
Comparator.comparingInt<Map.Entry<ModbusRegistryKey, String>?> { it.key.sectionNumber }
|
|
||||||
.thenComparingInt { it.key.address }
|
|
||||||
)
|
|
||||||
.forEach { (key, description) ->
|
|
||||||
val typeString = when (key) {
|
|
||||||
is ModbusRegistryKey.Coil -> "Coil"
|
|
||||||
is ModbusRegistryKey.DiscreteInput -> "Discrete"
|
|
||||||
is ModbusRegistryKey.HoldingRegister -> "Register"
|
|
||||||
is ModbusRegistryKey.InputRegister -> "Input"
|
|
||||||
}
|
}
|
||||||
val rangeString = if (key.count == 1) {
|
|
||||||
key.address.toString()
|
|
||||||
} else {
|
|
||||||
"${key.address} - ${key.address + key.count - 1}"
|
|
||||||
}
|
|
||||||
to.appendLine("${typeString}\t$rangeString\t$description")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ModbusRegistryMap.toJson(): JsonArray = buildJsonArray {
|
public fun print(map: ModbusRegistryMap, to: Appendable = System.out) {
|
||||||
ModbusRegistryMap.validate(this@toJson)
|
validate(map)
|
||||||
entries.forEach { (key, description) ->
|
map.entries.entries
|
||||||
|
.sortedWith(
|
||||||
val entry = buildJsonObject {
|
Comparator.comparingInt<Map.Entry<ModbusRegistryKey, String>?> { it.key.sectionNumber }
|
||||||
put(
|
.thenComparingInt { it.key.address }
|
||||||
"type",
|
)
|
||||||
when (key) {
|
.forEach { (key, description) ->
|
||||||
is ModbusRegistryKey.Coil -> "Coil"
|
val typeString = when (key) {
|
||||||
is ModbusRegistryKey.DiscreteInput -> "Discrete"
|
is ModbusRegistryKey.Coil -> "Coil"
|
||||||
is ModbusRegistryKey.HoldingRegister -> "Register"
|
is ModbusRegistryKey.DiscreteInput -> "Discrete"
|
||||||
is ModbusRegistryKey.InputRegister -> "Input"
|
is ModbusRegistryKey.HoldingRegister -> "Register"
|
||||||
|
is ModbusRegistryKey.InputRegister -> "Input"
|
||||||
|
}
|
||||||
|
val rangeString = if (key.count == 1) {
|
||||||
|
key.address.toString()
|
||||||
|
} else {
|
||||||
|
"${key.address} - ${key.address + key.count - 1}"
|
||||||
|
}
|
||||||
|
to.appendLine("${typeString}\t$rangeString\t$description")
|
||||||
}
|
}
|
||||||
)
|
|
||||||
put("address", key.address)
|
|
||||||
if (key.count > 1) {
|
|
||||||
put("count", key.count)
|
|
||||||
}
|
|
||||||
put("description", description)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(entry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,8 @@ public open class OpcUaDeviceBySpec<D : Device>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun close() {
|
||||||
client.disconnect()
|
client.disconnect()
|
||||||
|
super<DeviceBySpec>.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,11 +73,11 @@ public class DeviceNameSpace(
|
|||||||
//for now, use DF paths as ids
|
//for now, use DF paths as ids
|
||||||
nodeId = newNodeId("${deviceName.tokens.joinToString("/")}/$propertyName")
|
nodeId = newNodeId("${deviceName.tokens.joinToString("/")}/$propertyName")
|
||||||
when {
|
when {
|
||||||
descriptor.readable && descriptor.mutable -> {
|
descriptor.readable && descriptor.writable -> {
|
||||||
setAccessLevel(AccessLevel.READ_WRITE)
|
setAccessLevel(AccessLevel.READ_WRITE)
|
||||||
setUserAccessLevel(AccessLevel.READ_WRITE)
|
setUserAccessLevel(AccessLevel.READ_WRITE)
|
||||||
}
|
}
|
||||||
descriptor.mutable -> {
|
descriptor.writable -> {
|
||||||
setAccessLevel(AccessLevel.WRITE_ONLY)
|
setAccessLevel(AccessLevel.WRITE_ONLY)
|
||||||
setUserAccessLevel(AccessLevel.WRITE_ONLY)
|
setUserAccessLevel(AccessLevel.WRITE_ONLY)
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,9 @@ class OpcUaClientTest {
|
|||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
fun testReadDouble() = runTest {
|
fun testReadDouble() = runTest {
|
||||||
val device = DemoOpcUaDevice.build()
|
DemoOpcUaDevice.build().use{
|
||||||
device.start()
|
println(it.read(DemoOpcUaDevice.randomDouble))
|
||||||
println(device.read(DemoOpcUaDevice.randomDouble))
|
}
|
||||||
device.stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -78,7 +78,7 @@ class DemoController : Controller(), ContextAware {
|
|||||||
logger.info { "Visualization server stopped" }
|
logger.info { "Visualization server stopped" }
|
||||||
magixServer?.stop(1000, 5000)
|
magixServer?.stop(1000, 5000)
|
||||||
logger.info { "Magix server stopped" }
|
logger.info { "Magix server stopped" }
|
||||||
device?.stop()
|
device?.close()
|
||||||
logger.info { "Device server stopped" }
|
logger.info { "Device server stopped" }
|
||||||
context.close()
|
context.close()
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Compa
|
|||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.NUMBER)
|
type(ValueType.NUMBER)
|
||||||
}
|
}
|
||||||
description = "Real to virtual time scale"
|
info = "Real to virtual time scale"
|
||||||
}
|
}
|
||||||
|
|
||||||
val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState)
|
val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState)
|
||||||
|
@ -14,6 +14,7 @@ import space.kscience.dataforge.names.Name
|
|||||||
import space.kscience.magix.api.MagixEndpoint
|
import space.kscience.magix.api.MagixEndpoint
|
||||||
import space.kscience.magix.api.subscribe
|
import space.kscience.magix.api.subscribe
|
||||||
import space.kscience.magix.rsocket.rSocketWithWebSockets
|
import space.kscience.magix.rsocket.rSocketWithWebSockets
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) {
|
class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) {
|
||||||
|
|
||||||
@ -30,13 +31,17 @@ class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun onStart() {
|
@OptIn(ExperimentalTime::class)
|
||||||
|
override suspend fun open() {
|
||||||
|
super.open()
|
||||||
|
|
||||||
val magixEndpoint = MagixEndpoint.rSocketWithWebSockets(
|
val magixEndpoint = MagixEndpoint.rSocketWithWebSockets(
|
||||||
meta["magixServerHost"].string ?: "localhost",
|
meta["magixServerHost"].string ?: "localhost",
|
||||||
)
|
)
|
||||||
|
|
||||||
magixEndpoint.launchMagixVirtualCarUpdate()
|
launch {
|
||||||
|
magixEndpoint.launchMagixVirtualCarUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : Factory<MagixVirtualCar> {
|
companion object : Factory<MagixVirtualCar> {
|
||||||
|
@ -100,7 +100,8 @@ open class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(I
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
override suspend fun onStart() {
|
override suspend fun open() {
|
||||||
|
super<DeviceBySpec>.open()
|
||||||
//initializing the clock
|
//initializing the clock
|
||||||
timeState = Clock.System.now()
|
timeState = Clock.System.now()
|
||||||
//starting regular updates
|
//starting regular updates
|
||||||
|
@ -71,9 +71,9 @@ class VirtualCarController : Controller(), ContextAware {
|
|||||||
logger.info { "Shutting down..." }
|
logger.info { "Shutting down..." }
|
||||||
magixServer?.stop(1000, 5000)
|
magixServer?.stop(1000, 5000)
|
||||||
logger.info { "Magix server stopped" }
|
logger.info { "Magix server stopped" }
|
||||||
magixVirtualCar?.stop()
|
magixVirtualCar?.close()
|
||||||
logger.info { "Magix virtual car server stopped" }
|
logger.info { "Magix virtual car server stopped" }
|
||||||
virtualCar?.stop()
|
virtualCar?.close()
|
||||||
logger.info { "Virtual car server stopped" }
|
logger.info { "Virtual car server stopped" }
|
||||||
context.close()
|
context.close()
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ class PiMotionMasterDevice(
|
|||||||
override fun build(context: Context, meta: Meta): PiMotionMasterDevice = PiMotionMasterDevice(context)
|
override fun build(context: Context, meta: Meta): PiMotionMasterDevice = PiMotionMasterDevice(context)
|
||||||
|
|
||||||
val connected by booleanProperty(descriptorBuilder = {
|
val connected by booleanProperty(descriptorBuilder = {
|
||||||
description = "True if the connection address is defined and the device is initialized"
|
info = "True if the connection address is defined and the device is initialized"
|
||||||
}) {
|
}) {
|
||||||
port != null
|
port != null
|
||||||
}
|
}
|
||||||
@ -201,7 +201,7 @@ class PiMotionMasterDevice(
|
|||||||
|
|
||||||
|
|
||||||
val timeout by mutableProperty(MetaConverter.duration, PiMotionMasterDevice::timeoutValue) {
|
val timeout by mutableProperty(MetaConverter.duration, PiMotionMasterDevice::timeoutValue) {
|
||||||
description = "Timeout"
|
info = "Timeout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ class PiMotionMasterDevice(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val enabled by axisBooleanProperty("EAX") {
|
val enabled by axisBooleanProperty("EAX") {
|
||||||
description = "Motor enable state."
|
info = "Motor enable state."
|
||||||
}
|
}
|
||||||
|
|
||||||
val halt by unitAction {
|
val halt by unitAction {
|
||||||
@ -275,20 +275,20 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val targetPosition by axisNumberProperty("MOV") {
|
val targetPosition by axisNumberProperty("MOV") {
|
||||||
description = """
|
info = """
|
||||||
Sets a new absolute target position for the specified axis.
|
Sets a new absolute target position for the specified axis.
|
||||||
Servo mode must be switched on for the commanded axis prior to using this command (closed-loop operation).
|
Servo mode must be switched on for the commanded axis prior to using this command (closed-loop operation).
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|
||||||
val onTarget by booleanProperty({
|
val onTarget by booleanProperty({
|
||||||
description = "Queries the on-target state of the specified axis."
|
info = "Queries the on-target state of the specified axis."
|
||||||
}) {
|
}) {
|
||||||
readAxisBoolean("ONT?")
|
readAxisBoolean("ONT?")
|
||||||
}
|
}
|
||||||
|
|
||||||
val reference by booleanProperty({
|
val reference by booleanProperty({
|
||||||
description = "Get Referencing Result"
|
info = "Get Referencing Result"
|
||||||
}) {
|
}) {
|
||||||
readAxisBoolean("FRF?")
|
readAxisBoolean("FRF?")
|
||||||
}
|
}
|
||||||
@ -298,36 +298,36 @@ class PiMotionMasterDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val minPosition by doubleProperty({
|
val minPosition by doubleProperty({
|
||||||
description = "Minimal position value for the axis"
|
info = "Minimal position value for the axis"
|
||||||
}) {
|
}) {
|
||||||
mm.requestAndParse("TMN?", axisId)[axisId]?.toDoubleOrNull()
|
mm.requestAndParse("TMN?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
?: error("Malformed `TMN?` response. Should include float value for $axisId")
|
?: error("Malformed `TMN?` response. Should include float value for $axisId")
|
||||||
}
|
}
|
||||||
|
|
||||||
val maxPosition by doubleProperty({
|
val maxPosition by doubleProperty({
|
||||||
description = "Maximal position value for the axis"
|
info = "Maximal position value for the axis"
|
||||||
}) {
|
}) {
|
||||||
mm.requestAndParse("TMX?", axisId)[axisId]?.toDoubleOrNull()
|
mm.requestAndParse("TMX?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
?: error("Malformed `TMX?` response. Should include float value for $axisId")
|
?: error("Malformed `TMX?` response. Should include float value for $axisId")
|
||||||
}
|
}
|
||||||
|
|
||||||
val position by doubleProperty({
|
val position by doubleProperty({
|
||||||
description = "The current axis position."
|
info = "The current axis position."
|
||||||
}) {
|
}) {
|
||||||
mm.requestAndParse("POS?", axisId)[axisId]?.toDoubleOrNull()
|
mm.requestAndParse("POS?", axisId)[axisId]?.toDoubleOrNull()
|
||||||
?: error("Malformed `POS?` response. Should include float value for $axisId")
|
?: error("Malformed `POS?` response. Should include float value for $axisId")
|
||||||
}
|
}
|
||||||
|
|
||||||
val openLoopTarget by axisNumberProperty("OMA") {
|
val openLoopTarget by axisNumberProperty("OMA") {
|
||||||
description = "Position for open-loop operation."
|
info = "Position for open-loop operation."
|
||||||
}
|
}
|
||||||
|
|
||||||
val closedLoop by axisBooleanProperty("SVO") {
|
val closedLoop by axisBooleanProperty("SVO") {
|
||||||
description = "Servo closed loop mode"
|
info = "Servo closed loop mode"
|
||||||
}
|
}
|
||||||
|
|
||||||
val velocity by axisNumberProperty("VEL") {
|
val velocity by axisNumberProperty("VEL") {
|
||||||
description = "Velocity value for closed-loop operation"
|
info = "Velocity value for closed-loop operation"
|
||||||
}
|
}
|
||||||
|
|
||||||
val move by action(MetaConverter.meta, MetaConverter.unit) {
|
val move by action(MetaConverter.meta, MetaConverter.unit) {
|
||||||
|
@ -10,4 +10,4 @@ publishing.sonatype=false
|
|||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.jvmargs=-Xmx4096m
|
org.gradle.jvmargs=-Xmx4096m
|
||||||
|
|
||||||
toolsVersion=0.15.0-kotlin-1.9.20-RC2
|
toolsVersion=0.14.10-kotlin-1.9.0
|
@ -50,7 +50,6 @@ include(
|
|||||||
// ":controls-mongo",
|
// ":controls-mongo",
|
||||||
":controls-storage",
|
":controls-storage",
|
||||||
":controls-storage:controls-xodus",
|
":controls-storage:controls-xodus",
|
||||||
":controls-constructor",
|
|
||||||
":magix",
|
":magix",
|
||||||
":magix:magix-api",
|
":magix:magix-api",
|
||||||
":magix:magix-server",
|
":magix:magix-server",
|
||||||
|
Loading…
Reference in New Issue
Block a user