From 1c3f8477b1609714eb6a489d35b78a8b35e070ec Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 25 Aug 2024 13:13:56 -0300 Subject: [PATCH 1/5] [api]: Create DustCap interface and events --- .../main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt | 6 ++++++ .../nebulosa/indi/device/dustcap/DustCapAttached.kt | 5 +++++ .../nebulosa/indi/device/dustcap/DustCapCanParkChanged.kt | 5 +++++ .../nebulosa/indi/device/dustcap/DustCapDetached.kt | 5 +++++ .../kotlin/nebulosa/indi/device/dustcap/DustCapEvent.kt | 8 ++++++++ .../nebulosa/indi/device/dustcap/DustCapParkChanged.kt | 5 +++++ 6 files changed, 34 insertions(+) create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapAttached.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapCanParkChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapDetached.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapEvent.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapParkChanged.kt diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt new file mode 100644 index 000000000..ad690989a --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt @@ -0,0 +1,6 @@ +package nebulosa.indi.device.dustcap + +import nebulosa.indi.device.Device +import nebulosa.indi.device.Parkable + +interface DustCap : Device, Parkable diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapAttached.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapAttached.kt new file mode 100644 index 000000000..db85bb17e --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapAttached.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.dustcap + +import nebulosa.indi.device.DeviceAttached + +data class DustCapAttached(override val device: DustCap) : DustCapEvent, DeviceAttached diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapCanParkChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapCanParkChanged.kt new file mode 100644 index 000000000..d63bac45f --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapCanParkChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.dustcap + +import nebulosa.indi.device.PropertyChangedEvent + +data class DustCapCanParkChanged(override val device: DustCap) : DustCapEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapDetached.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapDetached.kt new file mode 100644 index 000000000..98a7a0d68 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapDetached.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.dustcap + +import nebulosa.indi.device.DeviceDetached + +data class DustCapDetached(override val device: DustCap) : DustCapEvent, DeviceDetached diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapEvent.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapEvent.kt new file mode 100644 index 000000000..4831075aa --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapEvent.kt @@ -0,0 +1,8 @@ +package nebulosa.indi.device.dustcap + +import nebulosa.indi.device.DeviceEvent + +interface DustCapEvent : DeviceEvent { + + override val device: DustCap +} diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapParkChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapParkChanged.kt new file mode 100644 index 000000000..79229fc84 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCapParkChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.dustcap + +import nebulosa.indi.device.PropertyChangedEvent + +data class DustCapParkChanged(override val device: DustCap) : DustCapEvent, PropertyChangedEvent From 398744d180af2c5f89aa38e046d096ca7a61e05d Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 25 Aug 2024 13:31:19 -0300 Subject: [PATCH 2/5] [api]: Implement INDI DustCap --- .../kotlin/nebulosa/indi/client/INDIClient.kt | 6 ++ .../device/INDIDeviceProtocolHandler.kt | 22 +++++++ .../indi/client/device/dustcap/INDIDustCap.kt | 63 +++++++++++++++++++ .../indi/device/AbstractINDIDeviceProvider.kt | 26 ++++++++ .../kotlin/nebulosa/indi/device/DeviceType.kt | 1 + .../indi/device/INDIDeviceProvider.kt | 5 ++ .../nebulosa/indi/device/dustcap/DustCap.kt | 7 ++- 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/dustcap/INDIDustCap.kt diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/INDIClient.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/INDIClient.kt index 5351b639e..5d0019e56 100644 --- a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/INDIClient.kt +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/INDIClient.kt @@ -9,6 +9,7 @@ import nebulosa.indi.client.device.camera.AsiCamera import nebulosa.indi.client.device.camera.INDICamera import nebulosa.indi.client.device.camera.SVBonyCamera import nebulosa.indi.client.device.camera.SimCamera +import nebulosa.indi.client.device.dustcap.INDIDustCap import nebulosa.indi.client.device.focuser.INDIFocuser import nebulosa.indi.client.device.mount.INDIMount import nebulosa.indi.client.device.rotator.INDIRotator @@ -16,6 +17,7 @@ import nebulosa.indi.client.device.wheel.INDIFilterWheel import nebulosa.indi.device.Device import nebulosa.indi.device.INDIDeviceProvider import nebulosa.indi.device.camera.Camera +import nebulosa.indi.device.dustcap.DustCap import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS @@ -76,6 +78,10 @@ data class INDIClient(val connection: INDIConnection) : INDIDeviceProtocolHandle return INDIGuideOutput(this, name) } + override fun newDustCap(name: String, executable: String): DustCap { + return INDIDustCap(this, name) + } + override fun start() { super.start() sendMessageToServer(GetProperties()) diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/INDIDeviceProtocolHandler.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/INDIDeviceProtocolHandler.kt index d17a4d1f7..4ad216491 100644 --- a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/INDIDeviceProtocolHandler.kt +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/INDIDeviceProtocolHandler.kt @@ -5,6 +5,7 @@ import nebulosa.indi.device.Device import nebulosa.indi.device.DeviceMessageReceived import nebulosa.indi.device.MessageSender import nebulosa.indi.device.camera.Camera +import nebulosa.indi.device.dustcap.DustCap import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS @@ -46,6 +47,8 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message protected abstract fun newGuideOutput(name: String, executable: String): GuideOutput + protected abstract fun newDustCap(name: String, executable: String): DustCap + private fun registerCamera(message: TextVector<*>): Camera? { val executable = message["DRIVER_EXEC"]?.value @@ -116,6 +119,16 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message } } + private fun registerDustCap(message: TextVector<*>): DustCap? { + val executable = message["DRIVER_EXEC"]?.value + + return if (!executable.isNullOrEmpty() && message.device.isNotEmpty() && dustCap(message.device) == null) { + newDustCap(message.device, executable).also(::registerDustCap) + } else { + null + } + } + open fun start() { if (protocolReader == null) { protocolReader = INDIProtocolReader(this, Thread.MIN_PRIORITY) @@ -220,6 +233,14 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message } } + if (DeviceInterfaceType.isDustCap(interfaceType)) { + registerDustCap(message)?.also { + registered = true + it.handleMessage(message) + takeMessageFromReorderingQueue(it) + } + } + if (!registered) { LOG.warn("device is not registered. name={}, interface={}", message.device, interfaceType) notRegisteredDevices.add(message.device) @@ -258,6 +279,7 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message is Rotator -> unregisterRotator(device) is GPS -> unregisterGPS(device) is GuideOutput -> unregisterGuideOutput(device) + is DustCap -> unregisterDustCap(device) } } diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/dustcap/INDIDustCap.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/dustcap/INDIDustCap.kt new file mode 100644 index 000000000..269287c06 --- /dev/null +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/dustcap/INDIDustCap.kt @@ -0,0 +1,63 @@ +package nebulosa.indi.client.device.dustcap + +import nebulosa.indi.client.INDIClient +import nebulosa.indi.client.device.INDIDevice +import nebulosa.indi.device.dustcap.DustCap +import nebulosa.indi.device.dustcap.DustCapCanParkChanged +import nebulosa.indi.device.dustcap.DustCapParkChanged +import nebulosa.indi.device.firstOnSwitchOrNull +import nebulosa.indi.protocol.DefSwitchVector +import nebulosa.indi.protocol.DefVector.Companion.isNotReadOnly +import nebulosa.indi.protocol.INDIProtocol +import nebulosa.indi.protocol.SwitchVector +import nebulosa.indi.protocol.Vector.Companion.isBusy + +internal open class INDIDustCap( + override val sender: INDIClient, + override val name: String, +) : INDIDevice(), DustCap { + + @Volatile final override var canPark = false + @Volatile final override var parking = false + @Volatile final override var parked = false + + override fun handleMessage(message: INDIProtocol) { + when (message) { + is SwitchVector<*> -> { + when (message.name) { + "CAP_PARK" -> { + if (message is DefSwitchVector) { + canPark = message.isNotReadOnly + + sender.fireOnEventReceived(DustCapCanParkChanged(this)) + } + + parking = message.isBusy + parked = message.firstOnSwitchOrNull()?.name == "PARK" + + sender.fireOnEventReceived(DustCapParkChanged(this)) + } + } + } + else -> Unit + } + + super.handleMessage(message) + } + + override fun park() { + if (canPark) { + sendNewSwitch("CAP_PARK", "PARK" to true) + } + } + + override fun unpark() { + if (canPark) { + sendNewSwitch("CAP_PARK", "UNPARK" to true) + } + } + + override fun close() = Unit + + override fun toString() = "DustCap(name=$name, connected=$connected, parking=$parking, parked=$parked)" +} diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/AbstractINDIDeviceProvider.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/AbstractINDIDeviceProvider.kt index fab2e2334..d763f3d31 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/AbstractINDIDeviceProvider.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/AbstractINDIDeviceProvider.kt @@ -4,6 +4,9 @@ import nebulosa.indi.device.camera.Camera import nebulosa.indi.device.camera.CameraAttached import nebulosa.indi.device.camera.CameraDetached import nebulosa.indi.device.camera.GuideHead +import nebulosa.indi.device.dustcap.DustCap +import nebulosa.indi.device.dustcap.DustCapAttached +import nebulosa.indi.device.dustcap.DustCapDetached import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.filterwheel.FilterWheelAttached import nebulosa.indi.device.filterwheel.FilterWheelDetached @@ -38,6 +41,7 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { private val rotators = HashMap(2) private val gps = HashMap(2) private val guideOutputs = HashMap(2) + private val dustCaps = HashMap(2) private val thermometers = HashMap(2) override fun registerDeviceEventHandler(handler: DeviceEventHandler) = handlers.add(handler) @@ -57,6 +61,7 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { rotator(id)?.also(devices::add) gps(id)?.also(devices::add) guideOutput(id)?.also(devices::add) + dustCap(id)?.also(devices::add) thermometer(id)?.also(devices::add) return devices } @@ -89,6 +94,10 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { override fun guideOutput(id: String) = guideOutputs[id] + override fun dustCaps() = dustCaps.values.toSet() + + override fun dustCap(id: String) = dustCaps[id] + override fun thermometers() = thermometers.values.toSet() override fun thermometer(id: String) = thermometers[id] @@ -213,6 +222,21 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { LOG.info("guide output detached: {} ({})", device.name, device.id) } + fun registerDustCap(device: DustCap): Boolean { + if (device.id in dustCaps) return false + dustCaps[device.id] = device + dustCaps[device.name] = device + fireOnEventReceived(DustCapAttached(device)) + LOG.info("dust cap attached: {} ({})", device.name, device.id) + return true + } + + fun unregisterDustCap(device: DustCap) { + dustCaps.remove(device.name) + fireOnEventReceived(DustCapDetached(dustCaps.remove(device.id) ?: return)) + LOG.info("dust cap detached: {} ({})", device.name, device.id) + } + fun registerThermometer(device: Thermometer): Boolean { if (device.id in thermometers) return false thermometers[device.id] = device @@ -236,6 +260,7 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { rotators().onEach(Device::close).onEach(::unregisterRotator) gps().onEach(Device::close).onEach(::unregisterGPS) guideOutputs().onEach(Device::close).onEach(::unregisterGuideOutput) + dustCaps().onEach(Device::close).onEach(::unregisterDustCap) cameras.clear() mounts.clear() @@ -244,6 +269,7 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { rotators.clear() gps.clear() guideOutputs.clear() + dustCaps.clear() thermometers.clear() handlers.clear() diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/DeviceType.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/DeviceType.kt index f13fb3073..5d22f1f35 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/DeviceType.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/DeviceType.kt @@ -10,4 +10,5 @@ enum class DeviceType(@JvmField val code: String) { DOME("DOM"), SWITCH("SWT"), GUIDE_OUTPUT("GDT"), + DUST_CAP("DCP"), } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/INDIDeviceProvider.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/INDIDeviceProvider.kt index 6ceb4a5df..15fb9e4ad 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/INDIDeviceProvider.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/INDIDeviceProvider.kt @@ -1,6 +1,7 @@ package nebulosa.indi.device import nebulosa.indi.device.camera.Camera +import nebulosa.indi.device.dustcap.DustCap import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS @@ -49,6 +50,10 @@ interface INDIDeviceProvider : MessageSender, AutoCloseable { fun guideOutput(id: String): GuideOutput? + fun dustCaps(): Collection + + fun dustCap(id: String): DustCap? + fun thermometers(): Collection fun thermometer(id: String): Thermometer? diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt index ad690989a..2e62dee78 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/dustcap/DustCap.kt @@ -1,6 +1,11 @@ package nebulosa.indi.device.dustcap import nebulosa.indi.device.Device +import nebulosa.indi.device.DeviceType import nebulosa.indi.device.Parkable -interface DustCap : Device, Parkable +interface DustCap : Device, Parkable { + + override val type + get() = DeviceType.DUST_CAP +} From 015eb4fcdfdfb347a7365ec7b2b40cc35c935049 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 25 Aug 2024 14:04:47 -0300 Subject: [PATCH 3/5] [api]: Implement DustCap endpoints --- ...viceOrEntityParamMethodArgumentResolver.kt | 2 + .../api/connection/ConnectionService.kt | 18 ++++++++ .../nebulosa/api/dustcap/DustCapController.kt | 46 +++++++++++++++++++ .../api/dustcap/DustCapDeserializer.kt | 16 +++++++ .../nebulosa/api/dustcap/DustCapEventAware.kt | 8 ++++ .../nebulosa/api/dustcap/DustCapEventHub.kt | 36 +++++++++++++++ .../api/dustcap/DustCapMessageEvent.kt | 6 +++ .../nebulosa/api/dustcap/DustCapSerializer.kt | 24 ++++++++++ .../nebulosa/api/dustcap/DustCapService.kt | 28 +++++++++++ .../api/focusers/FocuserMessageEvent.kt | 3 +- .../api/guiding/GuideOutputMessageEvent.kt | 3 +- .../nebulosa/api/wheels/WheelMessageEvent.kt | 3 +- 12 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapDeserializer.kt create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventAware.kt create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventHub.kt create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapMessageEvent.kt create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapSerializer.kt create mode 100644 api/src/main/kotlin/nebulosa/api/dustcap/DustCapService.kt diff --git a/api/src/main/kotlin/nebulosa/api/beans/converters/device/DeviceOrEntityParamMethodArgumentResolver.kt b/api/src/main/kotlin/nebulosa/api/beans/converters/device/DeviceOrEntityParamMethodArgumentResolver.kt index 5769939e2..d8da5d002 100644 --- a/api/src/main/kotlin/nebulosa/api/beans/converters/device/DeviceOrEntityParamMethodArgumentResolver.kt +++ b/api/src/main/kotlin/nebulosa/api/beans/converters/device/DeviceOrEntityParamMethodArgumentResolver.kt @@ -9,6 +9,7 @@ import nebulosa.api.calibration.CalibrationFrameRepository import nebulosa.api.connection.ConnectionService import nebulosa.indi.device.Device import nebulosa.indi.device.camera.Camera +import nebulosa.indi.device.dustcap.DustCap import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS @@ -41,6 +42,7 @@ class DeviceOrEntityParamMethodArgumentResolver( Rotator::class.java to { connectionService.rotator(it) }, GPS::class.java to { connectionService.gps(it) }, GuideOutput::class.java to { connectionService.guideOutput(it) }, + DustCap::class.java to { connectionService.dustCap(it) }, Thermometer::class.java to { connectionService.thermometer(it) }, ) diff --git a/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt b/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt index 7b803bcc4..23d9df643 100644 --- a/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt +++ b/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt @@ -8,6 +8,7 @@ import nebulosa.indi.device.Device import nebulosa.indi.device.DeviceEventHandler import nebulosa.indi.device.INDIDeviceProvider import nebulosa.indi.device.camera.Camera +import nebulosa.indi.device.dustcap.DustCap import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS @@ -134,6 +135,10 @@ class ConnectionService( return providers[id]?.guideOutputs() ?: emptyList() } + fun dustCaps(id: String): Collection { + return providers[id]?.dustCaps() ?: emptyList() + } + fun thermometers(id: String): Collection { return providers[id]?.thermometers() ?: emptyList() } @@ -166,6 +171,10 @@ class ConnectionService( return providers.values.flatMap { it.guideOutputs() } } + fun dustCaps(): List { + return providers.values.flatMap { it.dustCaps() } + } + fun thermometers(): List { return providers.values.flatMap { it.thermometers() } } @@ -198,6 +207,10 @@ class ConnectionService( return providers[id]?.guideOutput(name) } + fun dustCap(id: String, name: String): DustCap? { + return providers[id]?.dustCap(name) + } + fun thermometer(id: String, name: String): Thermometer? { return providers[id]?.thermometer(name) } @@ -230,6 +243,10 @@ class ConnectionService( return providers.firstNotNullOfOrNull { it.value.guideOutput(name) } } + fun dustCap(name: String): DustCap? { + return providers.firstNotNullOfOrNull { it.value.dustCap(name) } + } + fun thermometer(name: String): Thermometer? { return providers.firstNotNullOfOrNull { it.value.thermometer(name) } } @@ -241,6 +258,7 @@ class ConnectionService( ?: wheel(name) ?: rotator(name) ?: guideOutput(name) + ?: dustCap(name) ?: gps(name) ?: thermometer(name) } diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt new file mode 100644 index 000000000..f6a9e4c23 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt @@ -0,0 +1,46 @@ +package nebulosa.api.dustcap + +import nebulosa.api.connection.ConnectionService +import nebulosa.indi.device.dustcap.DustCap +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("dust-cap") +class DustCapController( + private val connectionService: ConnectionService, + private val dustCapService: DustCapService, +) { + + @GetMapping + fun dustCaps(): List { + return connectionService.dustCaps().sorted() + } + + @GetMapping("{dustCap}") + fun dustCap(dustCap: DustCap): DustCap { + return dustCap + } + + @PutMapping("{dustCap}/connect") + fun connect(dustCap: DustCap) { + dustCapService.connect(dustCap) + } + + @PutMapping("{dustCap}/disconnect") + fun disconnect(dustCap: DustCap) { + dustCapService.disconnect(dustCap) + } + + @PutMapping("{dustCap}/park") + fun park(dustCap: DustCap) { + dustCapService.park(dustCap) + } + + @PutMapping("{dustCap}/unpark") + fun unpark(dustCap: DustCap) { + dustCapService.unpark(dustCap) + } +} diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapDeserializer.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapDeserializer.kt new file mode 100644 index 000000000..0a0d6b81f --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapDeserializer.kt @@ -0,0 +1,16 @@ +package nebulosa.api.dustcap + +import nebulosa.api.beans.converters.device.DeviceDeserializer +import nebulosa.api.connection.ConnectionService +import nebulosa.indi.device.dustcap.DustCap +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Lazy +import org.springframework.stereotype.Component + +@Component +class DustCapDeserializer : DeviceDeserializer(DustCap::class.java) { + + @Autowired @Lazy private lateinit var connectionService: ConnectionService + + override fun deviceFor(name: String) = connectionService.dustCap(name) +} diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventAware.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventAware.kt new file mode 100644 index 000000000..bb78ac070 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventAware.kt @@ -0,0 +1,8 @@ +package nebulosa.api.dustcap + +import nebulosa.indi.device.dustcap.DustCapEvent + +fun interface DustCapEventAware { + + fun handleDustCapEvent(event: DustCapEvent) +} diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventHub.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventHub.kt new file mode 100644 index 000000000..53328e7ac --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapEventHub.kt @@ -0,0 +1,36 @@ +package nebulosa.api.dustcap + +import nebulosa.api.beans.annotations.Subscriber +import nebulosa.api.devices.DeviceEventHub +import nebulosa.api.message.MessageService +import nebulosa.indi.device.DeviceType +import nebulosa.indi.device.PropertyChangedEvent +import nebulosa.indi.device.dustcap.DustCap +import nebulosa.indi.device.dustcap.DustCapAttached +import nebulosa.indi.device.dustcap.DustCapDetached +import nebulosa.indi.device.dustcap.DustCapEvent +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import org.springframework.stereotype.Component + +@Component +@Subscriber +class DustCapEventHub( + private val messageService: MessageService, +) : DeviceEventHub(DeviceType.DUST_CAP), DustCapEventAware { + + @Subscribe(threadMode = ThreadMode.ASYNC) + override fun handleDustCapEvent(event: DustCapEvent) { + if (event.device.type == DeviceType.DUST_CAP) { + when (event) { + is PropertyChangedEvent -> onNext(event) + is DustCapAttached -> onAttached(event.device) + is DustCapDetached -> onDetached(event.device) + } + } + } + + override fun sendMessage(eventName: String, device: DustCap) { + messageService.sendMessage(DustCapMessageEvent(eventName, device)) + } +} diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapMessageEvent.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapMessageEvent.kt new file mode 100644 index 000000000..97be6acf6 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapMessageEvent.kt @@ -0,0 +1,6 @@ +package nebulosa.api.dustcap + +import nebulosa.api.devices.DeviceMessageEvent +import nebulosa.indi.device.dustcap.DustCap + +data class DustCapMessageEvent(override val eventName: String, override val device: DustCap) : DeviceMessageEvent diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapSerializer.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapSerializer.kt new file mode 100644 index 000000000..46fbad20a --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapSerializer.kt @@ -0,0 +1,24 @@ +package nebulosa.api.dustcap + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import nebulosa.indi.device.dustcap.DustCap +import org.springframework.stereotype.Component + +@Component +class DustCapSerializer : StdSerializer(DustCap::class.java) { + + override fun serialize(value: DustCap, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeStartObject() + gen.writeStringField("type", value.type.name) + gen.writeStringField("sender", value.sender.id) + gen.writeStringField("id", value.id) + gen.writeStringField("name", value.name) + gen.writeBooleanField("connected", value.connected) + gen.writeBooleanField("canPark", value.canPark) + gen.writeBooleanField("parking", value.parking) + gen.writeBooleanField("parked", value.parked) + gen.writeEndObject() + } +} diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapService.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapService.kt new file mode 100644 index 000000000..3883fca12 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapService.kt @@ -0,0 +1,28 @@ +package nebulosa.api.dustcap + +import nebulosa.indi.device.dustcap.DustCap +import org.springframework.stereotype.Service + +@Service +class DustCapService(private val dustCapEventHub: DustCapEventHub) { + + fun connect(dustCap: DustCap) { + dustCap.connect() + } + + fun disconnect(dustCap: DustCap) { + dustCap.disconnect() + } + + fun park(dustCap: DustCap) { + dustCap.park() + } + + fun unpark(dustCap: DustCap) { + dustCap.unpark() + } + + fun listen(dustCap: DustCap) { + dustCapEventHub.listen(dustCap) + } +} diff --git a/api/src/main/kotlin/nebulosa/api/focusers/FocuserMessageEvent.kt b/api/src/main/kotlin/nebulosa/api/focusers/FocuserMessageEvent.kt index 4a595146b..a7e0bc00a 100644 --- a/api/src/main/kotlin/nebulosa/api/focusers/FocuserMessageEvent.kt +++ b/api/src/main/kotlin/nebulosa/api/focusers/FocuserMessageEvent.kt @@ -3,5 +3,4 @@ package nebulosa.api.focusers import nebulosa.api.devices.DeviceMessageEvent import nebulosa.indi.device.focuser.Focuser -data class FocuserMessageEvent(override val eventName: String, override val device: Focuser) : - DeviceMessageEvent +data class FocuserMessageEvent(override val eventName: String, override val device: Focuser) : DeviceMessageEvent diff --git a/api/src/main/kotlin/nebulosa/api/guiding/GuideOutputMessageEvent.kt b/api/src/main/kotlin/nebulosa/api/guiding/GuideOutputMessageEvent.kt index 07ad123b4..282801769 100644 --- a/api/src/main/kotlin/nebulosa/api/guiding/GuideOutputMessageEvent.kt +++ b/api/src/main/kotlin/nebulosa/api/guiding/GuideOutputMessageEvent.kt @@ -3,5 +3,4 @@ package nebulosa.api.guiding import nebulosa.api.devices.DeviceMessageEvent import nebulosa.indi.device.guider.GuideOutput -data class GuideOutputMessageEvent(override val eventName: String, override val device: GuideOutput) : - DeviceMessageEvent +data class GuideOutputMessageEvent(override val eventName: String, override val device: GuideOutput) : DeviceMessageEvent diff --git a/api/src/main/kotlin/nebulosa/api/wheels/WheelMessageEvent.kt b/api/src/main/kotlin/nebulosa/api/wheels/WheelMessageEvent.kt index f1856e125..943f353c3 100644 --- a/api/src/main/kotlin/nebulosa/api/wheels/WheelMessageEvent.kt +++ b/api/src/main/kotlin/nebulosa/api/wheels/WheelMessageEvent.kt @@ -3,5 +3,4 @@ package nebulosa.api.wheels import nebulosa.api.devices.DeviceMessageEvent import nebulosa.indi.device.filterwheel.FilterWheel -data class WheelMessageEvent(override val eventName: String, override val device: FilterWheel) : - DeviceMessageEvent +data class WheelMessageEvent(override val eventName: String, override val device: FilterWheel) : DeviceMessageEvent From 55d0f80e77aad54ca8de9d083035cf92c6777296 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Wed, 28 Aug 2024 14:17:14 -0300 Subject: [PATCH 4/5] [api][desktop]: Implement DustCap --- .../api/connection/ConnectionEventHub.kt | 8 ++ .../nebulosa/api/dustcap/DustCapController.kt | 7 +- desktop/README.md | 8 ++ desktop/dust-cap.png | Bin 0 -> 6979 bytes desktop/light-box.png | Bin 0 -> 12847 bytes desktop/src/app/about/about.component.ts | 1 + desktop/src/app/app-routing.module.ts | 5 ++ desktop/src/app/app.module.ts | 2 + .../src/app/dustcap/dustcap.component.html | 54 ++++++++++++ desktop/src/app/dustcap/dustcap.component.ts | 83 ++++++++++++++++++ desktop/src/app/home/home.component.html | 10 +++ desktop/src/app/home/home.component.ts | 69 ++++++++++----- desktop/src/app/indi/indi.component.ts | 2 + .../src/app/lightbox/lightbox.component.html | 4 +- .../src/app/lightbox/lightbox.component.ts | 31 +------ desktop/src/assets/icons/lid.png | Bin 0 -> 3043 bytes desktop/src/shared/pipes/enum.pipe.ts | 1 + desktop/src/shared/services/api.service.ts | 31 +++++++ .../shared/services/browser-window.service.ts | 6 ++ .../src/shared/services/electron.service.ts | 4 + .../src/shared/services/preference.service.ts | 5 -- desktop/src/shared/types/device.types.ts | 2 +- desktop/src/shared/types/dustcap.types.ts | 21 +++++ desktop/src/shared/types/lightbox.types.ts | 14 --- 24 files changed, 298 insertions(+), 70 deletions(-) create mode 100644 desktop/dust-cap.png create mode 100644 desktop/light-box.png create mode 100644 desktop/src/app/dustcap/dustcap.component.html create mode 100644 desktop/src/app/dustcap/dustcap.component.ts create mode 100644 desktop/src/assets/icons/lid.png create mode 100644 desktop/src/shared/types/dustcap.types.ts diff --git a/api/src/main/kotlin/nebulosa/api/connection/ConnectionEventHub.kt b/api/src/main/kotlin/nebulosa/api/connection/ConnectionEventHub.kt index d492782bd..238f10b78 100644 --- a/api/src/main/kotlin/nebulosa/api/connection/ConnectionEventHub.kt +++ b/api/src/main/kotlin/nebulosa/api/connection/ConnectionEventHub.kt @@ -1,8 +1,10 @@ package nebulosa.api.connection import nebulosa.api.cameras.CameraEventHub +import nebulosa.api.dustcap.DustCapEventHub import nebulosa.api.focusers.FocuserEventHub import nebulosa.api.guiding.GuideOutputEventHub +import nebulosa.api.lightboxes.LightBoxEventHub import nebulosa.api.mounts.MountEventHub import nebulosa.api.rotators.RotatorEventHub import nebulosa.api.wheels.WheelEventHub @@ -10,9 +12,11 @@ import nebulosa.indi.device.DeviceConnectionEvent import nebulosa.indi.device.DeviceEvent import nebulosa.indi.device.DeviceEventHandler import nebulosa.indi.device.camera.Camera +import nebulosa.indi.device.dustcap.DustCap import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.guider.GuideOutput +import nebulosa.indi.device.lightbox.LightBox import nebulosa.indi.device.mount.Mount import nebulosa.indi.device.rotator.Rotator import org.springframework.stereotype.Component @@ -25,6 +29,8 @@ class ConnectionEventHub( private val wheelEventHub: WheelEventHub, private val guideOutputEventHub: GuideOutputEventHub, private val rotatorEventHub: RotatorEventHub, + private val lightBoxEventHub: LightBoxEventHub, + private val dustCapEventHub: DustCapEventHub, ) : DeviceEventHandler.EventReceived { override fun onEventReceived(event: DeviceEvent<*>) { @@ -37,6 +43,8 @@ class ConnectionEventHub( if (device is FilterWheel) wheelEventHub.onConnectionChanged(device) if (device is Rotator) rotatorEventHub.onConnectionChanged(device) if (device is GuideOutput) guideOutputEventHub.onConnectionChanged(device) + if (device is LightBox) lightBoxEventHub.onConnectionChanged(device) + if (device is DustCap) dustCapEventHub.onConnectionChanged(device) } } } diff --git a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt index f6a9e4c23..67f4f40a0 100644 --- a/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt +++ b/api/src/main/kotlin/nebulosa/api/dustcap/DustCapController.kt @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("dust-cap") +@RequestMapping("dust-caps") class DustCapController( private val connectionService: ConnectionService, private val dustCapService: DustCapService, @@ -43,4 +43,9 @@ class DustCapController( fun unpark(dustCap: DustCap) { dustCapService.unpark(dustCap) } + + @PutMapping("{dustCap}/listen") + fun listen(dustCap: DustCap) { + dustCapService.listen(dustCap) + } } diff --git a/desktop/README.md b/desktop/README.md index 244054bc3..22dad9dd9 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -26,6 +26,14 @@ The complete integrated solution for all of your astronomical imaging needs. ![](rotator.png) +## Light Box + +![](light-box.png) + +## Dust Cap + +![](dust-cap.png) + ## Guider ![](guider.png) diff --git a/desktop/dust-cap.png b/desktop/dust-cap.png new file mode 100644 index 0000000000000000000000000000000000000000..6e808f0f6405b6e8663a2e3d9b2a013e197cca48 GIT binary patch literal 6979 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 literal 0 HcmV?d00001 diff --git a/desktop/light-box.png b/desktop/light-box.png new file mode 100644 index 0000000000000000000000000000000000000000..88f098715ab88aae398fe024b7f7334c5676854a GIT binary patch literal 12847 zcmbVzbyStnw=F6HB1(6MARyf>-3`*+-5eUE8wo`?bO=btAq7Ob8>B(HyX$W5`{#{u z$M}tRAA^Cy+26PKxA$6e&NbIL5lRY@Z;%O);o#ujNK1*Sz`;GE0RR7sgaCdj{yy*p z|G>M5NUI@%FCQe+aPT|6tGK4Cs)M;J#K_qU&cfco&Wypu#M#Wu-o?_v_3%ZjAQ(jb zG)UCh%*fTs!JbUb%FYbV$<2t2m7C1S!hwvHnU$T4g`Jm~nU|S+9_!;jI5;vmX)$3n zkF@;-Z`~xbhsR^wb|S-omFTPhXEfCJ|Fqb3;__1pSjQP|?aa^rS~E;L*zK0p`a*1W zZOR(AYt#3#4%|luq)PeoeiV9gVkyC+qph(Iz_FpY+}sPZea`yB%uH0ax!Sgx&35U# zkcl69se9>WaOvA5E-tQx4lnFT9#Q-n4PF>cmP6Of5$kglxX}bw_UB7RIj<0!P%t$r?XX z7}32pXEKxtd_GV4{n(ndd#p-|@7usPhoLWS&Tk-d&XlIekGJP6V`D>(%M0Qi#Rukt z7iK65`PL)=Sgc%wNAKvGC#rNF;Bq3C!RIcT^(o=XeEPds;9pb76cqM z*m9?c&u)l!&?Rct@}~7W(mCZTQ+-u=<}yG?`pwF%^Cd~AD+|o z+;7rN#dxRYW0G359a)ulT?iNpYKnDaBztVZDB1Tk#4DDo*>UdfTK_dORawV9n>>45 zFMYQXs2Plj80sf9^K z;QSS>?f3MKdPmL22RhE7zXLf=$9M|A`hMDV+MSWO8n>BfR(K$wb5X^!IN;J7Tu16o zt4*3k=vDt<=BeY6-=I|>o*A~tJfxW}$$9*OXIk5$YBHDbQ)%YnIH{$uV>-;lbacM; zjK{pg^AAkV$GLDV^rei{wQleT6BSjk7_0EiRrO~FCs}B5VQ+PqoUSvh$4-fEB*jEm|{BXap(9uZP z)8DhPoULlPu$pq)Ek59DNjofu()GnD_}&va^n-rie1&eyf-}76qxc8kLfR0dH+U{Q zKJz`_yOWtIhY}M8Y-K)_G5!>ADUeh$&?n}Ne%W`UjJ`FU%oX-_MOX-RP2sN!?xX!1 z^Z7%4leGO-8hx`9e}B(fk2%>Ayc4Dec5hlHR9;1M)zlAj!vzLr7(ynD{Tu0~x7qWA zT@ijWKZMi!;|Kkh^A(*(2|gX^mQQP49I5CxAw~}ScFFcnI|-Vj>uhx9ESyr%f6KJ6 zAHex0*pP(J=ijdpGUS2Uh(Q^GxMakkSh05e?&yGb%J0Maj3ff)L}zOPp@pf27qajR z$o-aLbq?TXfn~l${uN*GX&OY74Nv526uFnI;xjD8{QA)e<{WrTxDV;dpM!{ zF|4QfV`j4RRcs=<*CytNl!N{i$aMTap~FpPx|QEM>psqv`}D{%X&pyMX6pkCqn29WAoCcnnXvAt@9PR0Z6IdIeGi7S%G} z9P($RR|l+w5OV9!cD>Y!#*3-Dc41HbEp8C_`KKfGO}(l$^FK_qUU;)tgli4u< zgF}eR_#K@6fOVfFToe>`fXIKsZvvyu`}%knV8O0RYS6J~5Mwm3)l$EnMy8Gl9K6!1Cl%!Ekb>;>XmqAX?)supXGW}3JFQqH4ZD~9k( z?0WLxv8bN9DaW!aUW8WejM$TQC6yi?lm4X=1(y;slYTGvbq+Tx{2*Mx;phV{e%pq= z3^yCi>K*k(=_vEAE2VPx7d`2Uk9-iZGwEn}v&oWsu5CcF zjZgM=X_{j>eslY@U8WWXb@`#&0xbq1{SU7Ei45zJ+3ymX@V6+Up2wJ+y7<5I{xTlrzRnFgiY6Wi^v+SH`)Y~m^E=e!^MJPz!cPj~76BhxDgEvrY!V3b zbzh2i{~8*+rVdfo8p0??ER%w-ZF+hF zMo5IOoKs_VFVO0btbFQ9jN*7xJ!K^vz4nc~n=&d@cPHxN+l5slP~T9w-oTal6`0IB z99RTkj6C#op`vCrt@31$TII2CN^;&^a{rS`)8Y4$r#i9>wxp(S`{?D?^$dH zl{D1>;|A;`B4m{8j07gaEIyh*qO=#EVCS>i#~IgiI2whas&ecYmbC=yx3Aa!6bvzoryn+rT?R1 zEZjF^6t~DSS|LM$XLMs%ffR){5i$URLptZWR)oY<@SY{P|vDqha(#0n!` z*b`ajQAb3YJ36KM3px&Vb`?rqKyQ|?1w4wx7wPzrU<@qu!9NxfK_ag#dRsslwjVmfsCHCO}6tjSvDW(1f_b%kkZuKkVc;ze6eQ)LO z8Ud_yNHvBNZs!g+H&qEsFH`nn7i`T6&rtMVcQmc?wYBI4Po!#6wB%gzG=xrWHeL( zjh=ipE&ipVz_20gZOS`bGZVgQTu7D0mPm#U>#JO?{%r=rdM5&zDXu2GqY*ZKAri;w z_?vZvnrN#(^c)F(6v@(Yg2OJ@5l;P^3!vmGPfLg7goH5(RfMzAJpdm5vLa^lL(^iP|DYVWHr-IX?ji<}>i1Li&@L5;HP}Zq9dKAtAw~ zUNl0j$MU4X9BeKd15n0XrT3bRuC%M&agaXFm)Q(3uedF!LyOVoQ2N_)A^Ul5zsK7p zE+h)5YJtLW2c}|$&$V+-ZthHt)wq1J8+DOdUsnuaM0E682(G^2`Of6L_hIv|hnwAx z{RR*hS}f$J%h~Mb>&;6yH#VktU%nDytMNh;xcFioA0OY=`gref za(e1{cig?-&!}A!J~5%DqN0MwX^R&p^uQg3!|sjCrdFUsHri_e?5^Y#yB8}qH6|*p# z`2O{~tJS!R%cB^s#oz7iLDTx)F=uD4U`gHd<-Y=T8EGge0`Km81h3aHhcbnRJ0dWl z*M!*cn>OPtIg`a2TrFJKXi zKBw{sZwNYmd7d5}AFs5d3g!L!mG(~dTh^yy&&vf!yiER^+E+?iTD?Eji}BdZQM%(u zH<|peU*qHB`y6-T#xRl+6A$`7oRYpJ;qSS-K9!b}Grc}pzq-CY-m4zwycQr5^x+m1 z6#R9wQ~bf8C5^{rgW3PVXJaV+_3v*CI#;caw?f}OX#D{*59~}9d+wIjT@5<;wYv-p zSUAuyGR9smddJBoGje#&1&(OXehB~d>z9<2l;Q2g{!@U-mI^$!-o^4$M!l~lQSa&M*@F<+$@WJbf|!|k*1 z@Nl@_mh2~o{*TbhC4bU@FJI6?QAuNE@{Ny{+n!gx9~O?B6=TCy&&=noLwVhu&bE{{22$E5id6CWJ(wS_hyCF^_F>cEad8&^IwI~?Q2i}y z%`;J`hdGqqyIK)ur{o`4s7LqkK1eVzZ~!>^M*dJhke3gb@XgoK3mbaah& zhh4RUjgNJc>X%Dgry%K%MnqAM=awF(T68A=@^Wx+Tuhtl{3tqX9`Oo=hhJfG@Ii4A zsXMUKmA@Um@(T+R5fx2gHH*A|1VIRU)F~Ku+8Dq$AI=y|VY9&Fb+t^Ko!C8x8%SXb z8y0#@i;Rr)8czQ9%^4&U!mg*cH)Uck9(bT?i{X}icZGCbaV;%;RG~X&ke(e~T^OXK z!zOV8Q(GryW@h|X%R$iu9FcCjQ!DH1A+4=KV353*UJUJr4^%8H@wW#}<%_qP$#HS0 zaLwMA<{%L+y5AtY_*DEAU}^mK?>*qChe1MX)jN`7L!R@3P9Hdp(7(7Q6lW=-O_3oKgZ>SKNcC?PO+#1RL1;XcobnbifV8T1up5ES- zuNwN~bacpBqYI^twRUq73JPzTTCOPC+S=sH*4L8_B#I|b%Kh)7Qd0@wZnIoXYP1<% z<9r|k0RI%4PtSDmd9w=(2Zz;Rk$=LPgI=R-4XlbS43*S;dn{kR>{!Ad;A&A((bH6I z9XW|$>Ophr;x@yx-*9pd?B)OFw`0vL$0*aR+&Iy9(=i0X@c00W#s`^ zQ0>y|JIF5}kRs^I3+1#kzB*d@pi?Us{AFYvZ@@f**9p&|C@C!0_&Rf3vdI)_a`J}& zuz{@a6RaKQ~@z3Cl-_L*mrDf zm;qT?WFR%<N4+;)bcw<$GTqBqb%y zUz~H~FLZTucB13sOB)#(RZlnTK>_MSKT%R#8l4|mrR znS_APK7amH5&ByuO>*m@R*ope(`_6 zd9C_G?&%l5`d(b4bI)bO zwtuN-d_0j{G?dx)FEcAE>yE(Y?ruK-jP2=iQh*EcH)-tR3urP78YZ>c*R{-X5!nLY zm)JrbUY`^mh>U-*#SBe%ziO$eNn&cf!w3;+E77X{TWd!Mhs&f7*ySZEDysQVS}$Oz zXZ3#fyx_U&o0?2Gp%F1Lu&@L>VrXGuA+%O?8ZheInirHUzMN;Db28xBi-6g2ULs{Z zy}{2f5NB<@W9wbFF#tl=g?+TO{m=wCq$Vf-Oeq;VaZ7x0umDX;!Yb2mmT`4u2b)qj zX^o7G%l2=0 zN_spSn&Q#bz?YF5r7xi7r=qv)6$5j<-P}GW|9K<0c`*8-#-v$|IW6@&u1?$E;h=V z4n*j_I$xidDP-_lIb3Fkk>up%1p~@qWn%-{rLDU=ES(QRk=zdg*>I`F_v~5HV>K#HbUMwYB>iTsHUGl$4YL0P3}ezG=6H0cb@(^AZ3=%EX%T0LBeDAR?eR1q>8mEwukc_NsrFJPE3c{LmgD-)UtZmT4 z6&8jACuaf*`2_iP6T>zI?mq|=+|Ie_PUy&fk14A+6esFsk=jlY83w^LUC<-|B(PUm z?p(Y5Os#n!3fk5>0q=~K>TrR0-)k~cx|RP5BE{Lc%9r}QbhaVB&G5G=s}VCE2#>3q zn_0#0u#km8-A30}09}44W-c0PCH4k)8n+oTXH8l^+arh26U2l9lx8*h0}m+02?D7T z+FV%?R#(TyiOOF@ymxn-1>t;qCB8tJsjd#gT(!2gCMX@gAhCvN1MOH?Qep~pz}fEf zaLZkc9sJ9eqaYZ-OtHzyxH9=CV2jMnF)%Sj<>cg=mySnsC8ZS<%BK6JrKOF4=ImK_ z2FajOlmzM%P=Lq@3qOO*J4rUzzDqC=Y~jk-Fy@5vIQ@eM!|O^bStN2t7hA+A+AP_5 zCt^p~6@ZJ3it2T~(+RVg-Vw-1O`XjOGlFT;zJG7n^MzcV1jW82eU6risqPZXly!z7 zd4Dt)5tmWd5L6q{cq}=+bKr=;60$A?3h4nz0vKs|eDJ;{t=s>_+N>X^QY1zZKXHp= zPZuL$awxD^ztCvUA19x!w7jtq3gi(;e{@QT3cz7HvE)|l1S+bk;OWf4-7kc`^HpZHuyO58nepD!(vtJ@r{(2Q0eS)XrrYcV6Gklogm84^2xn|;OmHT{q}LeS z+S)2QU7%7t2}H*#m`RrrS9qc?t=0wy53dIZZ4l)|ysm|9zw&@WX9WZmODd#)cswv5 z2XcN-;Qf^u7s@iw!{=Stw)?vEj-SCI=Up#L$`TSVNJnfk`d* z+Zr}IZ0Uep`YB3|+GMIkODccNOVS9?L>~aPEkJJ|EWu8f8MF>1(W=(F?-S4{q!~U9 zq{4Vh%sa7WH>(ewv8~NigskEQ$S>>*@57QoIQ0$$aa330c5}A9_qQ?#{DB$7a)3l!{i~tLbsF|q0*;@H zJhtGFnzu=F=-KmI00;E-_2IEwqM27;*GhK%P)P3sitZyw=HiJDNb+X8rL}#AZjvgO z$j|mi_jeMdsHmu^{{cx1h0ZZ3PGARL{OrGc>ar0#MTU_f?T7xyHrs!6DgI|y?mrHn zUT4Z+a%Ft>AaHd0s(rYC&reW5>DpD2@f?udDr5W#V?24hloKSG(kYQ5zGp-|IlE|} zKo=UO4Zag)m8EeeIk3z8c=ST+Jz7SLUf!HjFj{}`6ErIPC93QRk)&?EU$B7o8IGvv z#}9X=-Rlkr1`ER{j?XY=Fa=6&DgA1`i`eH0)fbQ$6Yx-i+VdUWNSCdVQt};2($V$L zy3VD4MWVuuwBEW5DClvQmm^T)oDz1dGo4lEq(bee}S-fZE@RAB&R65l5Gb1x?Kwor8mejtd#%k9z{HuZfm-oKx4lwmpX|4%WuL zVhxyA7x!;7gT2~U@zC_gxE-WmbxkUkb&~QMeKquh#^v2%O|k4Y+S7o@WY#Y$FE&S* zypZkf56Tz|I$6sPHkXefMV#T3c6G+DDOlp992>s(C`aSp;VtSf=;}p|cSyuij+Enu zDYVGiw<&^)7F;ebFCSl%UD&)##>+T@gS_=dXqcay%;!!o_!IZNhmSd>xL)76cXGW( zR>jKBKngy7nKkjyBldH%MMGosr+4vE5h#SA$zx&k*ED*13QZ=jDJpHK>pSittrB&= zAIcbW2hZXfa19Qb6Ive$aiXrqTI0F5)h%7(7B7vGk;$PX3!x{w-qYyIgxfnRv&5s6 z7_$?YE<%!Y>h+B%$%UMSJkV=W$8uLU+WlB?2!jLZjXwuP3Uhv<$%~Vi&FW617OQPf zg6j2y`D-`~$SVx#{kS^TaKlHmrFTMp<0OgPoW@7wz8!g1jRczxSmJBVcK#mF&z>fy?0;AsX;^aKJ3sF^8Uz@0P zkl4omD3VFraZV6dSGA4FQ06yqei$_LMqk zNkoc~--c7r9)-`xkb1)*aHeTZjQ##@? zt^%r?wm&xMkV+&1NB1={H8quyuTpcM1V_6wSa}$2d9Ju~Uz5RnD9?ZBpxvgh@Poal zk%CGQJ*B{|_nI?Lw($9;nAxWP@b#YEp!M*=0&#?xh?^UXt>5;tW57Cl66HWBy{7`5 z!M-5xjzTD7ZoZ7E$mwcLvXG+zCB`3(w4B-bb==YT`Y^F|e}Dh$+)*hrvuUUWRiKn3 zo&VAS)kSk_=RVfg{NwY7WL`1U6_E|WiCJBVw>KOGO!l_QI&@!=gW3W{EnQXIh=aI0 zMIC>}2l>SmNPdi(9r?6TeYutT7F~q%rFimRw<8!1suSRYySuwIHT7bi-iZ)s-X9tX1m{0QF zt%-w*QqnpM}Pj7NSMA`91ZCPAluwFgdhSd9WKkb$`)3yywi=D493u*9EE%{x2RbadM8%{yfi^cVnOG}Stkp5GG* zWy9nW#rwA96-o+yN33BJ6WoF?bOFb2Z*O-*&N88ki9~gZ#oCYF&4!xSmPgK@SO zPT%rlJ-jU~E@oWcN*PorE4&}IhB5FK{i5H?cnVS~R@?ga-3<99332sX9{&;!*jmk- z2Hk@WnOPcOvA5r;Eo`B)c4WJiCueM)g_Ni1D&OW$?-FO|P$l+$=`{s+^xa0=Ol1I$ z@70yZZOa9)HVnqb9fY>`GrSM78yi!7Z!cy@y*E-U{x-PKXlZG2=?#ObcD+Bj8u)+T zaF-(74mYmSxlf{ocq$WOh0CY;h~6WIAE$7Aw{sUf98KY=nu8tvT*z{np z(0YdqF9MKDGWpJydLbgCMZ6$+Te7R@>FtjwR}2X%Oc-^A<9yJ3G6bJYc%`PLyA7yAmGinxR~%^^ zP@~|Mzs|;FL8&&{>c4N_4kmuGeu2kt((}cji|wh}0K!D8()i`d$_h}jPKV8xCx2cu z9X;G0Hmz~EQ#m(0_g7C6-QB>24TT)=J(pxc+&ws0Eb5Nnm5Z}n?TR^fA9UpiU3wQFg%jJel_BR z^10#u)uQ-=KoQSp*ixa#lyf2)w$n>3Bz6PcClg2JCZ;2eW zq@K`;%lC{F`-F6|b7I?p zcqYxWr_@L>K(?h>V?Wx}Rx4H@}t_xZct}6Nc z!m_dr(#NZ?@m*Kf-wkYsscuDNRWFqm$QoIaY*$QR+WW&1AFX>v$;Q-lAd`&%$&r9KlP#L4NOjvDaXRB%QB9;<5bfsgI*Z zr(XOrKKC<4U|~=(;YWeSH&nF8qX>1P?vP~6<+&)hC4H1=XqzH#>%Xm;xGz#vnx&sU z;6x$i@)D&FyicYtVyWG~?+XkJ1P{;wIy9Y8xT~9+lKT2227cG?fXu#Frt~IJx2$v-w}}I&_Rx;7&AcYW}$2S*Kn|^fjDEIcQmpJD;k9(Q>!gY^-AM zt;wWT3}&K!MI~Sl2WWB%`g!EZ{aQ7a@xBR$nwoe|2G;4Yki)rNp-6dY=u~kMPe0__k zd^&AKI~RS|2$>kMWCuo!kFW3cQmc@F*IAZMojtH@5DMQg0R!UUX1cY?YMh#tH4OrT z8>O@^TtJL32y>_w-NJ7Qk66BXM+%TwVouhxej9i}7ZM=5s|MoW>Se(GF-j^@a=EAU?r z8a7ElPok%he^Qu6O;UhPVhRPB!^a*_DjVmi`rWrwcPUg8k_3uDN zrE3u=-Oiv3Pbf7B`52hE?nSyA&WXFU8*{+w-&Tw3re~F%L_QCjN|Z7^Lx$`Pk*0qR z3kz#+GN)GU7wXj>zQ1_nXo#tbWLLh$1@e?G=Ftw@av{&DH6M zf31R$b_~`AIJ;EynX)y#k&AvIq{ziKiG7AbQJ76?3P*Rw8@@Mv1yu-`B71rl7^(k) zr=dq@M?W;;sKTbs&tFke-g*yg40*F7Sqp^$>+tQ;Td9I^w^@>ShH&12vJ}c4gs}g1 z(+pwSEYQI%*QTQr-f-X>F(w1b09G!zv$DFnIEZcB;ySvSgRymcw< zITpPhMK{?dWE=8)3Bx2IAps_0*!1-7dHO>|vwZR^3d_HIRp(!9g=&wA2tQH;r&2TQ zHsO@|Z;lj%M{e;K_yJq)?*3lK)oP_|j4+T9M~pM(rHYA=$hes)aZc7%N&BvBK1np; z1D{Bc&gI@+E!kHY9oHlj-b?b^F4tb5rMs_jqOqTOcwMnMO}Jd$xVor_+kpxyJs=FddYjM+Hp}Q%zu0v_64?f-%{00DgpyxD z3O419678eak51a&u|G>nTz7Em^ver!a{gHDyNQ8H=abvk8Uj0ZgM!trZ5Qr$xw2NG z&`E1IZAUztXoc29TS-uVTB?6lH}mn{a_gfnt1V35%`Zxmx0w~_oefkdusL4miRO@y z>6hU_0aezH%!I~_+Yz=*KXEI;YSC*0?n;VHOX+f{U?pELBWLy$R>kRZF(_p@ zycY)M4||6MMv>KF(QSsHNm(_s3nKHWSTm=ill|*?q>e1>XMMsek(e>QSqC##XHeDB znkmr{jzY5rZ<)`Q-*zQ%C-Dfe5idxsxeMtxP}OzMcZ;%qme@P7rpJyh!Ub$QOP5Im zVuQm+LfopBmUZWK?1nTsyVhoMC`h$A{rQ%kKV^bP7{bXgbpM#LRz7a=5plPK%IK}u z!3$+JBwCA}b{o#7b{aQqUmKZMr&Lw3PFTZ`vUNbD)lXc@-u^w=S8*;QF1qv(KGqug zvG1s?K4{T&Dys)fXW;!aLZne8pKh*DFkNNxxFpSU`W8plu6(5+ikK$Y)>d)^IK9@e zRB+$X*VZ|VPJHCqCfqJ7Ho$~4UCX*Q-5_#v;~D&YN7sk!n$q+V zDR9K^Jr?rEz0_BMBX7Yp|N9RY{6`z}f9ZPu&*6Es{(tT7>A*Ut#NwVl*KqqtO!kV0 V +
+
+ + + +
+
+
+ + {{ dustCap.parking ? 'moving' : dustCap.parked ? 'closed' : 'open' }} +
+
+
+
+
+
+ @if (dustCap.parked) { + + } @else { + + } +
+
+
+ diff --git a/desktop/src/app/dustcap/dustcap.component.ts b/desktop/src/app/dustcap/dustcap.component.ts new file mode 100644 index 000000000..538ccfbb6 --- /dev/null +++ b/desktop/src/app/dustcap/dustcap.component.ts @@ -0,0 +1,83 @@ +import { AfterViewInit, Component, HostListener, NgZone, OnDestroy } from '@angular/core' +import { ActivatedRoute } from '@angular/router' +import { ApiService } from '../../shared/services/api.service' +import { ElectronService } from '../../shared/services/electron.service' +import { Tickable, Ticker } from '../../shared/services/ticker.service' +import { DEFAULT_DUST_CAP, DustCap } from '../../shared/types/dustcap.types' +import { AppComponent } from '../app.component' + +@Component({ + selector: 'neb-dustcap', + templateUrl: './dustcap.component.html', +}) +export class DustCapComponent implements AfterViewInit, OnDestroy, Tickable { + protected readonly dustCap = structuredClone(DEFAULT_DUST_CAP) + + constructor( + private readonly app: AppComponent, + private readonly api: ApiService, + electronService: ElectronService, + private readonly route: ActivatedRoute, + private readonly ticker: Ticker, + ngZone: NgZone, + ) { + app.title = 'Dust Cap' + + electronService.on('DUST_CAP.UPDATED', (event) => { + if (event.device.id === this.dustCap.id) { + ngZone.run(() => { + Object.assign(this.dustCap, event.device) + }) + } + }) + + electronService.on('DUST_CAP.DETACHED', (event) => { + if (event.device.id === this.dustCap.id) { + ngZone.run(() => { + Object.assign(this.dustCap, DEFAULT_DUST_CAP) + }) + } + }) + } + + ngAfterViewInit() { + this.route.queryParams.subscribe(async (e) => { + const data = JSON.parse(decodeURIComponent(e['data'] as string)) as DustCap + await this.dustCapChanged(data) + this.ticker.register(this, 30000) + }) + } + + @HostListener('window:unload') + ngOnDestroy() { + this.ticker.unregister(this) + } + + async tick() { + if (this.dustCap.id) { + await this.api.dustCapListen(this.dustCap) + } + } + + protected async dustCapChanged(dustCap?: DustCap) { + if (dustCap?.id) { + dustCap = await this.api.dustCap(dustCap.id) + Object.assign(this.dustCap, dustCap) + } + + this.app.subTitle = dustCap?.name ?? '' + } + + protected connect() { + if (this.dustCap.connected) { + return this.api.dustCapDisconnect(this.dustCap) + } else { + return this.api.dustCapConnect(this.dustCap) + } + } + + protected togglePark() { + if (this.dustCap.parked) return this.api.dustCapUnpark(this.dustCap) + else return this.api.dustCapPark(this.dustCap) + } +} diff --git a/desktop/src/app/home/home.component.html b/desktop/src/app/home/home.component.html index 2316ec03b..e2e1d31fa 100644 --- a/desktop/src/app/home/home.component.html +++ b/desktop/src/app/home/home.component.html @@ -191,6 +191,16 @@
Light Box
+
+ + +
Dust Cap
+
+
0 } + get hasDustCap() { + return this.dustCaps.length > 0 + } + get hasGuider() { return (this.hasCamera && this.hasMount) || this.hasGuideOutput } @@ -141,7 +147,7 @@ export class HomeComponent implements AfterContentInit { } get hasDevices() { - return this.hasCamera || this.hasMount || this.hasFocuser || this.hasWheel || this.hasDome || this.hasRotator || this.hasSwitch || this.hasGuideOutput || this.hasLightBox + return this.hasCamera || this.hasMount || this.hasFocuser || this.hasWheel || this.hasDome || this.hasRotator || this.hasSwitch || this.hasGuideOutput || this.hasLightBox || this.hasDustCap } get hasINDI() { @@ -275,6 +281,22 @@ export class HomeComponent implements AfterContentInit { }) }) + electronService.on('DUST_CAP.ATTACHED', (event) => { + ngZone.run(() => { + this.deviceAdded(event.device) + }) + }) + electronService.on(`DUST_CAP.DETACHED`, (event) => { + ngZone.run(() => { + this.deviceRemoved(event.device) + }) + }) + electronService.on(`DUST_CAP.UPDATED`, (event) => { + ngZone.run(() => { + this.deviceUpdated(event.device) + }) + }) + electronService.on('CONNECTION.CLOSED', async (event) => { if (this.connection?.id === event.id) { await ngZone.run(() => { @@ -297,6 +319,7 @@ export class HomeComponent implements AfterContentInit { this.rotators = await this.api.rotators() this.guideOutputs = await this.api.guideOutputs() this.lightBoxes = await this.api.lightBoxes() + this.dustCaps = await this.api.dustCaps() } void this.checkForNewVersion() @@ -331,6 +354,8 @@ export class HomeComponent implements AfterContentInit { this.guideOutputs.push(device) } else if (isLightBox(device)) { this.lightBoxes.push(device) + } else if (isDustCap(device)) { + this.dustCaps.push(device) } } @@ -356,6 +381,9 @@ export class HomeComponent implements AfterContentInit { } else if (isLightBox(device)) { const found = this.lightBoxes.findIndex((e) => e.id === device.id) this.lightBoxes.splice(found, 1) + } else if (isDustCap(device)) { + const found = this.dustCaps.findIndex((e) => e.id === device.id) + this.dustCaps.splice(found, 1) } } @@ -381,6 +409,9 @@ export class HomeComponent implements AfterContentInit { } else if (isLightBox(device)) { const found = this.lightBoxes.find((e) => e.id === device.id) found && Object.assign(found, device) + } else if (isDustCap(device)) { + const found = this.dustCaps.find((e) => e.id === device.id) + found && Object.assign(found, device) } } @@ -472,6 +503,7 @@ export class HomeComponent implements AfterContentInit { : type === 'WHEEL' ? this.wheels : type === 'ROTATOR' ? this.rotators : type === 'LIGHT_BOX' ? this.lightBoxes + : type === 'DUST_CAP' ? this.dustCaps : [] if (devices.length === 0) return @@ -484,25 +516,20 @@ export class HomeComponent implements AfterContentInit { } private async openDeviceWindow(device: Device) { - switch (device.type) { - case 'MOUNT': - await this.browserWindowService.openMount(device as Mount, { bringToFront: true }) - break - case 'CAMERA': - await this.browserWindowService.openCamera(device as Camera, { bringToFront: true }) - break - case 'FOCUSER': - await this.browserWindowService.openFocuser(device as Focuser, { bringToFront: true }) - break - case 'WHEEL': - await this.browserWindowService.openWheel(device as Wheel, { bringToFront: true }) - break - case 'ROTATOR': - await this.browserWindowService.openRotator(device as Rotator, { bringToFront: true }) - break - case 'LIGHT_BOX': - await this.browserWindowService.openLightBox(device as LightBox, { bringToFront: true }) - break + if (isMount(device)) { + await this.browserWindowService.openMount(device, { bringToFront: true }) + } else if (isCamera(device)) { + await this.browserWindowService.openCamera(device, { bringToFront: true }) + } else if (isFocuser(device)) { + await this.browserWindowService.openFocuser(device, { bringToFront: true }) + } else if (isWheel(device)) { + await this.browserWindowService.openWheel(device, { bringToFront: true }) + } else if (isRotator(device)) { + await this.browserWindowService.openRotator(device, { bringToFront: true }) + } else if (isLightBox(device)) { + await this.browserWindowService.openLightBox(device, { bringToFront: true }) + } else if (isDustCap(device)) { + await this.browserWindowService.openDustCap(device, { bringToFront: true }) } } @@ -525,6 +552,7 @@ export class HomeComponent implements AfterContentInit { case 'WHEEL': case 'ROTATOR': case 'LIGHT_BOX': + case 'DUST_CAP': await this.openDevice(type) break case 'GUIDER': @@ -621,6 +649,7 @@ export class HomeComponent implements AfterContentInit { this.domes = [] this.rotators = [] this.lightBoxes = [] + this.dustCaps = [] this.guideOutputs = [] this.switches = [] } diff --git a/desktop/src/app/indi/indi.component.ts b/desktop/src/app/indi/indi.component.ts index 0e195be11..908c9f8d4 100644 --- a/desktop/src/app/indi/indi.component.ts +++ b/desktop/src/app/indi/indi.component.ts @@ -88,6 +88,7 @@ export class INDIComponent implements AfterViewInit, OnDestroy { const rotators = await this.api.rotators() const guideOutputs = await this.api.guideOutputs() const lightBoxes = await this.api.lightBoxes() + const dustCaps = await this.api.dustCaps() const devices: Device[] = [] devices.push(...cameras.filter((a) => !devices.find((b) => a.name === b.name))) @@ -97,6 +98,7 @@ export class INDIComponent implements AfterViewInit, OnDestroy { devices.push(...rotators.filter((a) => !devices.find((b) => a.name === b.name))) devices.push(...guideOutputs.filter((a) => !devices.find((b) => a.name === b.name))) devices.push(...lightBoxes.filter((a) => !devices.find((b) => a.name === b.name))) + devices.push(...dustCaps.filter((a) => !devices.find((b) => a.name === b.name))) this.devices = devices.sort(deviceComparator) diff --git a/desktop/src/app/lightbox/lightbox.component.html b/desktop/src/app/lightbox/lightbox.component.html index c08423a2b..cd6c53cc2 100644 --- a/desktop/src/app/lightbox/lightbox.component.html +++ b/desktop/src/app/lightbox/lightbox.component.html @@ -28,8 +28,8 @@ Enabled + [ngModel]="lightBox.enabled" + (ngModelChange)="toggleEnable()" />
diff --git a/desktop/src/app/lightbox/lightbox.component.ts b/desktop/src/app/lightbox/lightbox.component.ts index 333f0794c..489061052 100644 --- a/desktop/src/app/lightbox/lightbox.component.ts +++ b/desktop/src/app/lightbox/lightbox.component.ts @@ -3,9 +3,8 @@ import { ActivatedRoute } from '@angular/router' import { debounceTime, Subject, Subscription } from 'rxjs' import { ApiService } from '../../shared/services/api.service' import { ElectronService } from '../../shared/services/electron.service' -import { PreferenceService } from '../../shared/services/preference.service' import { Tickable, Ticker } from '../../shared/services/ticker.service' -import { DEFAULT_LIGHT_BOX, DEFAULT_LIGHT_BOX_PREFERENCE, LightBox } from '../../shared/types/lightbox.types' +import { DEFAULT_LIGHT_BOX, LightBox } from '../../shared/types/lightbox.types' import { AppComponent } from '../app.component' @Component({ @@ -14,7 +13,6 @@ import { AppComponent } from '../app.component' }) export class LightBoxComponent implements AfterViewInit, OnDestroy, Tickable { protected readonly lightBox = structuredClone(DEFAULT_LIGHT_BOX) - protected readonly preference = structuredClone(DEFAULT_LIGHT_BOX_PREFERENCE) private readonly brightnessPublisher = new Subject() private readonly brightnessSubscription?: Subscription @@ -23,7 +21,6 @@ export class LightBoxComponent implements AfterViewInit, OnDestroy, Tickable { private readonly app: AppComponent, private readonly api: ApiService, electronService: ElectronService, - private readonly preferenceService: PreferenceService, private readonly route: ActivatedRoute, private readonly ticker: Ticker, ngZone: NgZone, @@ -34,7 +31,6 @@ export class LightBoxComponent implements AfterViewInit, OnDestroy, Tickable { if (event.device.id === this.lightBox.id) { ngZone.run(() => { Object.assign(this.lightBox, event.device) - this.update() }) } }) @@ -76,9 +72,6 @@ export class LightBoxComponent implements AfterViewInit, OnDestroy, Tickable { if (lightBox?.id) { lightBox = await this.api.lightBox(lightBox.id) Object.assign(this.lightBox, lightBox) - - this.loadPreference() - this.update() } this.app.subTitle = lightBox?.name ?? '' @@ -92,28 +85,12 @@ export class LightBoxComponent implements AfterViewInit, OnDestroy, Tickable { } } - protected toggleEnable(enabled: boolean) { - if (enabled) return this.api.lightBoxEnable(this.lightBox) - else return this.api.lightBoxDisable(this.lightBox) + protected toggleEnable() { + if (this.lightBox.enabled) return this.api.lightBoxDisable(this.lightBox) + else return this.api.lightBoxEnable(this.lightBox) } protected intensityChanged(intensity: number) { this.brightnessPublisher.next(intensity) - this.preference.intensity = intensity - this.savePreference() - } - - private update() {} - - private loadPreference() { - if (this.lightBox.id) { - Object.assign(this.preference, this.preferenceService.lightBox(this.lightBox).get()) - } - } - - protected savePreference() { - if (this.lightBox.connected) { - this.preferenceService.lightBox(this.lightBox).set(this.preference) - } } } diff --git a/desktop/src/assets/icons/lid.png b/desktop/src/assets/icons/lid.png new file mode 100644 index 0000000000000000000000000000000000000000..988b800274c1889de1bb4f9db6133c042af54172 GIT binary patch literal 3043 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 literal 0 HcmV?d00001 diff --git a/desktop/src/shared/pipes/enum.pipe.ts b/desktop/src/shared/pipes/enum.pipe.ts index e2448ad6e..3fee5fe00 100644 --- a/desktop/src/shared/pipes/enum.pipe.ts +++ b/desktop/src/shared/pipes/enum.pipe.ts @@ -157,6 +157,7 @@ export class EnumPipe implements PipeTransform { DOUBLE_OR_MULTIPLE_STAR: 'Double or Multiple Star', DOUBLE: 'Double', DRA: 'Draco', + DUST_CAP: 'Dust Cap', EAST: 'East', ECLIPSING_BINARY: 'Eclipsing Binary', EDUCATION: 'Education', diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts index 06c9a1d00..44ebede84 100644 --- a/desktop/src/shared/services/api.service.ts +++ b/desktop/src/shared/services/api.service.ts @@ -6,6 +6,7 @@ import { AutoFocusRequest } from '../types/autofocus.type' import { CalibrationFrame } from '../types/calibration.types' import { Camera, CameraStartCapture } from '../types/camera.types' import { Device, INDIProperty, INDISendProperty } from '../types/device.types' +import { DustCap } from '../types/dustcap.types' import { FlatWizardRequest } from '../types/flat-wizard.types' import { Focuser } from '../types/focuser.types' import { HipsSurvey } from '../types/framing.types' @@ -374,6 +375,36 @@ export class ApiService { return this.http.put(`light-boxes/${lightBox.id}/listen`) } + // DUST CAP + + dustCaps() { + return this.http.get(`dust-caps`) + } + + dustCap(id: string) { + return this.http.get(`dust-caps/${id}`) + } + + dustCapConnect(dustCap: DustCap) { + return this.http.put(`dust-caps/${dustCap.id}/connect`) + } + + dustCapDisconnect(dustCap: DustCap) { + return this.http.put(`dust-caps/${dustCap.id}/disconnect`) + } + + dustCapPark(dustCap: DustCap) { + return this.http.put(`dust-caps/${dustCap.id}/park`) + } + + dustCapUnpark(dustCap: DustCap) { + return this.http.put(`dust-caps/${dustCap.id}/unpark`) + } + + dustCapListen(dustCap: DustCap) { + return this.http.put(`dust-caps/${dustCap.id}/listen`) + } + // GUIDING guidingConnect(host: string = 'localhost', port: number = 4400) { diff --git a/desktop/src/shared/services/browser-window.service.ts b/desktop/src/shared/services/browser-window.service.ts index b0b8c31c3..e74e47959 100644 --- a/desktop/src/shared/services/browser-window.service.ts +++ b/desktop/src/shared/services/browser-window.service.ts @@ -3,6 +3,7 @@ import { OpenWindow, WindowPreference } from '../types/app.types' import { SkyAtlasInput } from '../types/atlas.types' import { Camera, CameraDialogInput, CameraStartCapture } from '../types/camera.types' import { Device } from '../types/device.types' +import { DustCap } from '../types/dustcap.types' import { Focuser } from '../types/focuser.types' import { LoadFraming } from '../types/framing.types' import { ImageSource, OpenImage } from '../types/image.types' @@ -67,6 +68,11 @@ export class BrowserWindowService { return this.openWindow({ preference, data, id: `lightbox.${data.name}`, path: 'light-box' }) } + openDustCap(data: DustCap, preference: WindowPreference = {}) { + Object.assign(preference, { icon: 'lid', width: 290, height: 169 }) + return this.openWindow({ preference, data, id: `dustcap.${data.name}`, path: 'dust-cap' }) + } + openWheelDialog(data: WheelDialogInput, preference: WindowPreference = {}) { Object.assign(preference, { icon: 'filter-wheel', width: 300, height: 217 }) return this.openModal({ diff --git a/desktop/src/shared/services/electron.service.ts b/desktop/src/shared/services/electron.service.ts index b532a223f..b61d62260 100644 --- a/desktop/src/shared/services/electron.service.ts +++ b/desktop/src/shared/services/electron.service.ts @@ -14,6 +14,7 @@ import { Location } from '../types/atlas.types' import { AutoFocusEvent } from '../types/autofocus.type' import { Camera, CameraCaptureEvent } from '../types/camera.types' import { INDIMessageEvent } from '../types/device.types' +import { DustCap } from '../types/dustcap.types' import { FlatWizardEvent } from '../types/flat-wizard.types' import { Focuser } from '../types/focuser.types' import { GuideOutput, Guider, GuiderHistoryStep, GuiderMessageEvent } from '../types/guider.types' @@ -67,6 +68,9 @@ export interface EventMap { 'LIGHT_BOX.UPDATED': DeviceMessageEvent 'LIGHT_BOX.ATTACHED': DeviceMessageEvent 'LIGHT_BOX.DETACHED': DeviceMessageEvent + 'DUST_CAP.UPDATED': DeviceMessageEvent + 'DUST_CAP.ATTACHED': DeviceMessageEvent + 'DUST_CAP.DETACHED': DeviceMessageEvent 'GUIDER.CONNECTED': GuiderMessageEvent 'GUIDER.DISCONNECTED': GuiderMessageEvent 'GUIDER.UPDATED': GuiderMessageEvent diff --git a/desktop/src/shared/services/preference.service.ts b/desktop/src/shared/services/preference.service.ts index 4acf173eb..ea9586ef5 100644 --- a/desktop/src/shared/services/preference.service.ts +++ b/desktop/src/shared/services/preference.service.ts @@ -10,7 +10,6 @@ import { DEFAULT_FRAMING_PREFERENCE, FramingPreference, framingPreferenceWithDef import { DEFAULT_GUIDER_PREFERENCE, GuiderPreference, guiderPreferenceWithDefault } from '../types/guider.types' import { DEFAULT_HOME_PREFERENCE, HomePreference, homePreferenceWithDefault } from '../types/home.types' import { DEFAULT_IMAGE_PREFERENCE, ImagePreference, imagePreferenceWithDefault } from '../types/image.types' -import { DEFAULT_LIGHT_BOX_PREFERENCE, LightBox, LightBoxPreference, lightBoxPreferenceWithDefault } from '../types/lightbox.types' import { DEFAULT_MOUNT_PREFERENCE, Mount, MountPreference, mountPreferenceWithDefault } from '../types/mount.types' import { DEFAULT_ROTATOR_PREFERENCE, Rotator, RotatorPreference, rotatorPreferenceWithDefault } from '../types/rotator.types' import { DEFAULT_SEQUENCER_PREFERENCE, SequencerPreference, sequencerPreferenceWithDefault } from '../types/sequencer.types' @@ -100,10 +99,6 @@ export class PreferenceService { return this.create(`rotator.${rotator.name}`, () => structuredClone(DEFAULT_ROTATOR_PREFERENCE), rotatorPreferenceWithDefault) } - lightBox(lightBox: LightBox) { - return this.create(`lightBox.${lightBox.name}`, () => structuredClone(DEFAULT_LIGHT_BOX_PREFERENCE), lightBoxPreferenceWithDefault) - } - flatWizard(camera: Camera) { return this.create(`flatWizard.${camera.name}`, () => structuredClone(DEFAULT_FLAT_WIZARD_PREFERENCE), flatWizardPreferenceWithDefault) } diff --git a/desktop/src/shared/types/device.types.ts b/desktop/src/shared/types/device.types.ts index 961c1afa5..b85e73746 100644 --- a/desktop/src/shared/types/device.types.ts +++ b/desktop/src/shared/types/device.types.ts @@ -8,7 +8,7 @@ export type INDIPropertyType = 'NUMBER' | 'SWITCH' | 'TEXT' export type SwitchRule = 'ONE_OF_MANY' | 'AT_MOST_ONE' | 'ANY_OF_MANY' -export type DeviceType = 'CAMERA' | 'MOUNT' | 'WHEEL' | 'FOCUSER' | 'ROTATOR' | 'GPS' | 'DOME' | 'SWITCH' | 'GUIDE_OUTPUT' | 'LIGHT_BOX' +export type DeviceType = 'CAMERA' | 'MOUNT' | 'WHEEL' | 'FOCUSER' | 'ROTATOR' | 'GPS' | 'DOME' | 'SWITCH' | 'GUIDE_OUTPUT' | 'LIGHT_BOX' | 'DUST_CAP' export interface Device { readonly type: DeviceType diff --git a/desktop/src/shared/types/dustcap.types.ts b/desktop/src/shared/types/dustcap.types.ts new file mode 100644 index 000000000..7fe4695fe --- /dev/null +++ b/desktop/src/shared/types/dustcap.types.ts @@ -0,0 +1,21 @@ +import type { Device } from './device.types' +import type { Parkable } from './mount.types' + +export type DustCap = Device & Parkable + +export const DEFAULT_DUST_CAP: DustCap = { + type: 'DUST_CAP', + sender: '', + id: '', + name: '', + driverName: '', + driverVersion: '', + connected: false, + canPark: false, + parking: false, + parked: false, +} + +export function isDustCap(device?: Device): device is DustCap { + return !!device && device.type === 'DUST_CAP' +} diff --git a/desktop/src/shared/types/lightbox.types.ts b/desktop/src/shared/types/lightbox.types.ts index e8343fe2c..77827424e 100644 --- a/desktop/src/shared/types/lightbox.types.ts +++ b/desktop/src/shared/types/lightbox.types.ts @@ -7,14 +7,6 @@ export interface LightBox extends Device { maxIntensity: number } -export interface LightBoxPreference { - intensity: number -} - -export const DEFAULT_LIGHT_BOX_PREFERENCE: LightBoxPreference = { - intensity: 0, -} - export const DEFAULT_LIGHT_BOX: LightBox = { enabled: false, intensity: 0, @@ -32,9 +24,3 @@ export const DEFAULT_LIGHT_BOX: LightBox = { export function isLightBox(device?: Device): device is LightBox { return !!device && device.type === 'LIGHT_BOX' } - -export function lightBoxPreferenceWithDefault(preference?: Partial, source: LightBoxPreference = DEFAULT_LIGHT_BOX_PREFERENCE) { - if (!preference) return structuredClone(source) - preference.intensity ??= source.intensity - return preference as LightBoxPreference -} From 1666fe10a640e7917699dcdab0e6d41771562023 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Wed, 28 Aug 2024 15:03:07 -0300 Subject: [PATCH 5/5] [desktop]: Add auxiliary button to Home --- desktop/home.png | Bin 36800 -> 36678 bytes desktop/src/app/about/about.component.ts | 1 + desktop/src/app/home/home.component.html | 58 ++++++++++++++--------- desktop/src/app/home/home.component.ts | 17 +++++-- desktop/src/assets/icons/toolkit.png | Bin 0 -> 2021 bytes desktop/src/shared/types/home.types.ts | 2 + 6 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 desktop/src/assets/icons/toolkit.png diff --git a/desktop/home.png b/desktop/home.png index 931ae2a3cfd41a3d49710d6cdff526489fb85505..33d8ffd26be15feae890c6cbcc6de71001c8c165 100644 GIT binary patch literal 36678 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 LNkvXXu0mjfh4LNd literal 36800 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 diff --git a/desktop/src/app/about/about.component.ts b/desktop/src/app/about/about.component.ts index 278432459..035606541 100644 --- a/desktop/src/app/about/about.component.ts +++ b/desktop/src/app/about/about.component.ts @@ -49,6 +49,7 @@ export class AboutComponent { this.icons.push({ link: `${FLAT_ICON_URL}/calibration_2364169`, name: 'Calibration', author: 'Freepik - Flaticon' }) this.icons.push({ link: `${FLAT_ICON_URL}/idea_3351801`, name: 'Bulb', author: 'Good Ware - Flaticon' }) this.icons.push({ link: `${FLAT_ICON_URL}/lid_7558659`, name: 'Lid', author: 'Nikita Golubev - Flaticon' }) + this.icons.push({ link: `${FLAT_ICON_URL}/toolkit_4229807`, name: 'Toolkit', author: 'Freepik - Flaticon' }) } private mapDependencies() { diff --git a/desktop/src/app/home/home.component.html b/desktop/src/app/home/home.component.html index e2e1d31fa..18d3f164e 100644 --- a/desktop/src/app/home/home.component.html +++ b/desktop/src/app/home/home.component.html @@ -175,32 +175,44 @@
- -
Switch
-
-
-
- - -
Light Box
-
-
-
- - -
Dust Cap
+ +
Auxiliary
+ @if (preference.showAuxiliary && hasAuxiliary) { +
+ + +
Switch
+
+
+
+ + +
Light Box
+
+
+
+ + +
Dust Cap
+
+
+ }
0 } - get hasSwitch() { - return this.switches.length > 0 - } - get hasRotator() { return this.rotators.length > 0 } @@ -118,6 +114,10 @@ export class HomeComponent implements AfterContentInit { return this.guideOutputs.length > 0 } + get hasSwitch() { + return this.switches.length > 0 + } + get hasLightBox() { return this.lightBoxes.length > 0 } @@ -126,6 +126,10 @@ export class HomeComponent implements AfterContentInit { return this.dustCaps.length > 0 } + get hasAuxiliary() { + return this.hasSwitch || this.hasLightBox || this.hasDustCap + } + get hasGuider() { return (this.hasCamera && this.hasMount) || this.hasGuideOutput } @@ -493,6 +497,11 @@ export class HomeComponent implements AfterContentInit { return DeviceChooserComponent.handleDisconnectDevice(this.api, event.device, event.item) } + protected toggleAuxiliary() { + this.preference.showAuxiliary = !this.preference.showAuxiliary + this.savePreference() + } + private async openDevice(type: DeviceType) { this.deviceModel.length = 0 diff --git a/desktop/src/assets/icons/toolkit.png b/desktop/src/assets/icons/toolkit.png new file mode 100644 index 0000000000000000000000000000000000000000..73b9742b4926ed7304074bed539d14b915ce6ad6 GIT binary patch literal 2021 zcmVg)J7DfsEdT!<^}a3tx(m6sv(LRC5_U?s-R?XRH8`GP#}(L zaKK>WZH8IyJzpQN9nZ|YGea|Yron&dN^`&SKi~PE@1EtJbM6Qg@chvizkPYse{6B9 z`-$CKb`ON{j=g)K)3$kBm6R{ukMC=5zgo;W-h2Kb)cUfZ{c4-<-n(qswIE+TJw35} z)7*og9RQ;8#vKbnYZ6W+Lf>G1f8UVT)*QE=1Rf6J zbg4RaZ~sdu?E*OJDDa)>oHu&kU*oI!9jE|U#(op{O_0A_vgKh>{nk|F(4A+lTzwUI zK1}B+aH7f&QfYRcPU{1m3As?IZU9!f#IBQFO&3SJHh|%@_o*;mr1!!-K>L(nVO255 z!(Rc<FcSxF~B+u}#(bOgXKS5Ljm`$IguD>T399128t2`DWg~{`=C1*S=3RkZ)mq zvYNuC!ywy(>^(g_F-5l(+XbyE025d)7ROGd^npsmvH=*)m@iHKPG!~Fbo9l~1@Tl$ z1y_Qgi(Wr>zOu>W*9QrQ3ujd|;)-;d{bD2OM@ z$BXXglyD65i*3f@-coP6J4}y{`E*#?Y9MBjEz;qpjQRRoqyC|=e4zmt>HEvP&PB20 zeVqv=uVwR{+lzU}-nrZiG#8ogKHk%R=j6>FRc(Z?73Wo~MI|=TwPBi2T@#uHG}}a* zYz$B^TjYIxFf32#ERbwTZrj-^Qer?h0N8}$AlO(IrznBGamEi7Bm;XED_^;5wOIeOE7gdbKuXZPn>*S@ixWB*&$*IttQV55L%|hw4tI1&Hpp2JT!Cgs zY`dfp7a}kKnm)b+=nPClOIpUd+ipt@=hJ&sc&-X^<<>jzNe!j8}Tw<1%}G1Qhu>z`O;U*a%u<* zXm+gra_J>V`R?kpaR|BG@xjc>pnMxwt{f4S(AQM}QFG*mYH*VgI1Bvn;$8DHYKLdC zq-36-gbID-3iOS8v1)ZQfWQ`zHBQ;V##h2uWnjwZ)zG~4K*iPI{|mf>2RB9*E&>08 zna>>@rscv#)O9;k-+z34^59QT6z3}lK&JaZr;WetfEl>`yuBc_u+L_XZaUPxFR&Z@ zD}QB0%54LnEgq#Q5}|K&3^VcE*QEcvHQ&8&q+DfEnSpkV7nbj)dOP#o`vRwC0R3C$ zN&9T!M4{u?(BW|eTw3hI`Db>1&v*R3*yp=WPjVc9T=)Le=_mx(4WFP(1fUTFZrSX*kflN!SG0Eri6h^_?$1% z+*%cV+43!N&+a$@);A(weEBUW^ZLG3VoVp%QeM_a2+DdeU76>$-(y+Ier~18e}GoY zclKK%?5ekt`a$ovfO++nQAf0u-4MAIFdKk+3#d0{15j@P^+v<21v0kb-PRQ25f9`3 z70nzkNSEXob|g)6&SB+n64#X9>8TbC6M$3mM;VGu?zIkqU$n1c0Z|x<<}sqI9xUF7 z{@>6rT_Ek0T}2mi-HLmiRij}7us9X1iYLJ0u~^N@ZI~|bvEd|9n&@kbLg;c)xZ`4$ z`Kj9NsWeOg0GhlgF{}KWdD6QKo0C@t46 zp;~hIJ0T`Qi<2rV*h-C}d9O4fZb3`Y>v7^LEEs^{ILuM-N;jh=cVtK7j!+*K-XNL{ zJr)FJyTEJ!W&==f0rkdg0O~EE-niuhQ2Vt&B!R?QYwi|6GsBRp`b+q#P}=~AU~hR8 zoOn&$P9c8{|L;G88m=yD8vs?b|7|20{%ygBa-SNgY31Q2bOCd^6egXTTR_gxldhb{6SFr z|5@oHu!h1dKM2kSpxy%N4GRd~q5KG-;#eet1)Q1zE%kv3{A=znUjdKKgs>Tb`e^n&@XIWn?Kvl+51!pHb&`-jvTK{7dlg+S(pbml zRYOfg{qcq5uPzZh1uZ