diff --git a/api/src/main/kotlin/nebulosa/api/alignment/polar/darv/DARVTask.kt b/api/src/main/kotlin/nebulosa/api/alignment/polar/darv/DARVTask.kt index 65e4ea05c..73842019a 100644 --- a/api/src/main/kotlin/nebulosa/api/alignment/polar/darv/DARVTask.kt +++ b/api/src/main/kotlin/nebulosa/api/alignment/polar/darv/DARVTask.kt @@ -17,6 +17,7 @@ import nebulosa.indi.device.camera.Camera import nebulosa.indi.device.camera.CameraEvent import nebulosa.indi.device.camera.FrameType import nebulosa.indi.device.guide.GuideOutput +import nebulosa.indi.device.mount.Mount import nebulosa.log.loggerFor import java.nio.file.Files import java.time.Duration @@ -64,7 +65,7 @@ data class DARVTask( override fun execute(cancellationToken: CancellationToken) { LOG.info("DARV started. camera={}, guideOutput={}, request={}", camera, guideOutput, request) - camera.snoop(listOf(guideOutput)) + if (guideOutput is Mount) camera.snoop(camera.snoopedDevices.filter { it !is Mount } + guideOutput) val task = SplitTask(listOf(cameraCaptureTask, Task.of(delayTask, forwardGuidePulseTask, backwardGuidePulseTask)), executor) task.execute(cancellationToken) diff --git a/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPATask.kt b/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPATask.kt index b3d8e926c..192faa152 100644 --- a/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPATask.kt +++ b/api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPATask.kt @@ -110,7 +110,7 @@ data class TPPATask( rightAscension = mount?.rightAscension ?: 0.0 declination = mount?.declination ?: 0.0 - camera.snoop(listOf(mount)) + camera.snoop(camera.snoopedDevices.filter { it !is Mount } + mount) cancellationToken.listenToPause(this) diff --git a/api/src/main/kotlin/nebulosa/api/autofocus/AutoFocusTask.kt b/api/src/main/kotlin/nebulosa/api/autofocus/AutoFocusTask.kt index d1231501b..17492d104 100644 --- a/api/src/main/kotlin/nebulosa/api/autofocus/AutoFocusTask.kt +++ b/api/src/main/kotlin/nebulosa/api/autofocus/AutoFocusTask.kt @@ -85,7 +85,7 @@ data class AutoFocusTask( var numberOfAttempts = 0 val maximumFocusPoints = request.capture.exposureAmount * request.initialOffsetSteps * 10 - // camera.snoop(listOf(focuser)) + camera.snoop(camera.snoopedDevices.filter { it !is Focuser } + focuser) while (!exited && !cancellationToken.isCancelled) { numberOfAttempts++ diff --git a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerController.kt b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerController.kt index fd36445da..17f9d715a 100644 --- a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerController.kt +++ b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerController.kt @@ -5,6 +5,7 @@ import nebulosa.indi.device.camera.Camera import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import org.springframework.web.bind.annotation.* @RestController @@ -16,9 +17,9 @@ class SequencerController( @PutMapping("{camera}/start") fun start( camera: Camera, - mount: Mount?, wheel: FilterWheel?, focuser: Focuser?, + mount: Mount?, wheel: FilterWheel?, focuser: Focuser?, rotator: Rotator?, @RequestBody @Valid body: SequencePlanRequest, - ) = sequencerService.start(camera, body, mount, wheel, focuser) + ) = sequencerService.start(camera, body, mount, wheel, focuser, rotator) @PutMapping("{camera}/stop") fun stop(camera: Camera) { diff --git a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerExecutor.kt b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerExecutor.kt index 48b10b2af..4f3bd05eb 100644 --- a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerExecutor.kt +++ b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerExecutor.kt @@ -16,6 +16,7 @@ import nebulosa.indi.device.filterwheel.FilterWheelEvent import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.focuser.FocuserEvent import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor @@ -54,7 +55,7 @@ class SequencerExecutor( fun execute( camera: Camera, request: SequencePlanRequest, - mount: Mount? = null, wheel: FilterWheel? = null, focuser: Focuser? = null, + mount: Mount? = null, wheel: FilterWheel? = null, focuser: Focuser? = null, rotator: Rotator? = null, ) { check(camera.connected) { "${camera.name} Camera is not connected" } check(jobs.none { it.task.camera === camera }) { "${camera.name} Sequencer Job is already in progress" } @@ -67,7 +68,11 @@ class SequencerExecutor( check(jobs.none { it.task.focuser === focuser }) { "${camera.name} Sequencer Job is already in progress" } } - val task = SequencerTask(camera, request, guider, mount, wheel, focuser, threadPoolTaskExecutor, calibrationFrameService) + if (rotator != null && rotator.connected) { + check(jobs.none { it.task.rotator === rotator }) { "${camera.name} Sequencer Job is already in progress" } + } + + val task = SequencerTask(camera, request, guider, mount, wheel, focuser, rotator, threadPoolTaskExecutor, calibrationFrameService) task.subscribe(this) with(SequencerJob(task)) { diff --git a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerService.kt b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerService.kt index fe26e8e24..871fcd1f3 100644 --- a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerService.kt +++ b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerService.kt @@ -4,6 +4,7 @@ import nebulosa.indi.device.camera.Camera import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import org.springframework.stereotype.Service import java.nio.file.Path import kotlin.io.path.exists @@ -18,13 +19,13 @@ class SequencerService( @Synchronized fun start( camera: Camera, request: SequencePlanRequest, - mount: Mount?, wheel: FilterWheel?, focuser: Focuser?, + mount: Mount?, wheel: FilterWheel?, focuser: Focuser?, rotator: Rotator?, ) { val savePath = request.savePath ?.takeIf { "$it".isNotBlank() && it.exists() && it.isDirectory() } ?: Path.of("$sequencesPath", (System.currentTimeMillis() / 1000).toString()) - sequencerExecutor.execute(camera, request.copy(savePath = savePath), mount, wheel, focuser) + sequencerExecutor.execute(camera, request.copy(savePath = savePath), mount, wheel, focuser, rotator) } @Synchronized diff --git a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerTask.kt b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerTask.kt index ecd8aca07..9b624c38d 100644 --- a/api/src/main/kotlin/nebulosa/api/sequencer/SequencerTask.kt +++ b/api/src/main/kotlin/nebulosa/api/sequencer/SequencerTask.kt @@ -19,6 +19,7 @@ import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.filterwheel.FilterWheelEvent import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import nebulosa.log.loggerFor import java.time.Duration import java.util.* @@ -37,6 +38,7 @@ data class SequencerTask( @JvmField val mount: Mount? = null, @JvmField val wheel: FilterWheel? = null, @JvmField val focuser: Focuser? = null, + @JvmField val rotator: Rotator? = null, private val executor: Executor? = null, private val calibrationFrameProvider: CalibrationFrameProvider? = null, ) : AbstractTask(), Consumer, CameraEventAware, WheelEventAware { @@ -131,7 +133,7 @@ data class SequencerTask( override fun execute(cancellationToken: CancellationToken) { LOG.info("Sequencer started. camera={}, mount={}, wheel={}, focuser={}, plan={}", camera, mount, wheel, focuser, plan) - camera.snoop(listOf(mount, wheel, focuser)) + camera.snoop(listOf(mount, wheel, focuser, rotator)) for (task in tasks) { if (cancellationToken.isCancelled) break diff --git a/desktop/src/app/sequencer/sequencer.component.html b/desktop/src/app/sequencer/sequencer.component.html index b8c5b9344..998f0830e 100644 --- a/desktop/src/app/sequencer/sequencer.component.html +++ b/desktop/src/app/sequencer/sequencer.component.html @@ -27,13 +27,8 @@ - - - - - - + @@ -144,6 +139,8 @@ (deviceChange)="wheelChanged()" /> +
diff --git a/desktop/src/app/sequencer/sequencer.component.ts b/desktop/src/app/sequencer/sequencer.component.ts index 8577473d3..43b06746c 100644 --- a/desktop/src/app/sequencer/sequencer.component.ts +++ b/desktop/src/app/sequencer/sequencer.component.ts @@ -13,6 +13,7 @@ import { JsonFile } from '../../shared/types/app.types' import { Camera, CameraCaptureEvent, CameraStartCapture } from '../../shared/types/camera.types' import { Focuser } from '../../shared/types/focuser.types' import { Mount } from '../../shared/types/mount.types' +import { Rotator } from '../../shared/types/rotator.types' import { EMPTY_SEQUENCE_PLAN, SEQUENCE_ENTRY_PROPERTIES, SequenceCaptureMode, SequenceEntryProperty, SequencePlan, SequencerEvent } from '../../shared/types/sequencer.types' import { FilterWheel } from '../../shared/types/wheel.types' import { deviceComparator } from '../../shared/utils/comparators' @@ -34,11 +35,13 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable mounts: Mount[] = [] wheels: FilterWheel[] = [] focusers: Focuser[] = [] + rotators: Rotator[] = [] camera?: Camera mount?: Mount wheel?: FilterWheel focuser?: Focuser + rotator?: Rotator readonly captureModes: SequenceCaptureMode[] = ['FULLY', 'INTERLEAVED'] readonly plan = structuredClone(EMPTY_SEQUENCE_PLAN) @@ -177,43 +180,53 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable }) electron.on('CAMERA.UPDATED', event => { - ngZone.run(() => { - const camera = this.cameras.find(e => e.id === event.device.id) + const camera = this.cameras.find(e => e.id === event.device.id) - if (camera) { + if (camera) { + ngZone.run(() => { Object.assign(camera, event.device) - } - }) + }) + } }) electron.on('MOUNT.UPDATED', event => { - ngZone.run(() => { - const mount = this.mounts.find(e => e.id === event.device.id) + const mount = this.mounts.find(e => e.id === event.device.id) - if (mount) { + if (mount) { + ngZone.run(() => { Object.assign(mount, event.device) - } - }) + }) + } }) electron.on('WHEEL.UPDATED', event => { - ngZone.run(() => { - const wheel = this.wheels.find(e => e.id === event.device.id) + const wheel = this.wheels.find(e => e.id === event.device.id) - if (wheel) { + if (wheel) { + ngZone.run(() => { Object.assign(wheel, event.device) - } - }) + }) + } }) electron.on('FOCUSER.UPDATED', event => { - ngZone.run(() => { - const focuser = this.focusers.find(e => e.id === event.device.id) + const focuser = this.focusers.find(e => e.id === event.device.id) - if (focuser) { + if (focuser) { + ngZone.run(() => { Object.assign(focuser, event.device) - } - }) + }) + } + }) + + electron.on('ROTATOR.UPDATED', event => { + const rotator = this.rotators.find(e => e.id === event.device.id) + + if (rotator) { + ngZone.run(() => { + Object.assign(rotator, event.device) + }) + } }) electron.on('SEQUENCER.ELAPSED', event => { @@ -246,6 +259,7 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable this.mounts = (await this.api.mounts()).sort(deviceComparator) this.wheels = (await this.api.wheels()).sort(deviceComparator) this.focusers = (await this.api.focusers()).sort(deviceComparator) + this.rotators = (await this.api.rotators()).sort(deviceComparator) this.loadSavedJsonFileFromPathOrAddDefault() @@ -262,6 +276,7 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable if (this.mount) this.api.mountListen(this.mount) if (this.focuser) this.api.focuserListen(this.focuser) if (this.wheel) this.api.wheelListen(this.wheel) + if (this.rotator) this.api.rotatorListen(this.rotator) } private enableOrDisableTopbarMenu(enable: boolean) { @@ -272,6 +287,7 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable const camera = this.camera ?? this.cameras[0] // const wheel = this.wheel ?? this.wheels[0] // const focuser = this.focuser ?? this.focusers[0] + // const rotator = this.rotator ?? this.rotators[0] this.plan.entries.push({ enabled: true, @@ -356,8 +372,9 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable this.camera = this.cameras.find(e => e.name === this.plan.camera?.name) ?? this.cameras[0] this.mount = this.mounts.find(e => e.name === this.plan.mount?.name) ?? this.mounts[0] - this.focuser = this.focusers.find(e => e.name === this.plan.focuser?.name) ?? this.focusers[0] this.wheel = this.wheels.find(e => e.name === this.plan.wheel?.name) ?? this.wheels[0] + this.focuser = this.focusers.find(e => e.name === this.plan.focuser?.name) ?? this.focusers[0] + this.rotator = this.rotators.find(e => e.name === this.plan.rotator?.name) ?? this.rotators[0] return plan.entries.length } @@ -377,16 +394,6 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable } } - async chooseSavePath() { - const defaultPath = this.plan.savePath - const path = await this.electron.openDirectory({ defaultPath }) - - if (path) { - this.plan.savePath = path - this.savePlan() - } - } - async showCameraDialog(entry: CameraStartCapture) { if (await CameraComponent.showAsDialog(this.browserWindow, 'SEQUENCER', this.camera!, entry)) { this.savePlan() @@ -415,11 +422,16 @@ export class SequencerComponent implements AfterContentInit, OnDestroy, Pingable this.ping() } + rotatorChanged() { + this.ping() + } + savePlan() { this.plan.camera = this.camera this.plan.mount = this.mount this.plan.wheel = this.wheel this.plan.focuser = this.focuser + this.plan.rotator = this.rotator this.storage.set(SEQUENCER_PLAN_KEY, this.plan) this.savedPathWasModified = !!this.savedPath } diff --git a/desktop/src/shared/types/sequencer.types.ts b/desktop/src/shared/types/sequencer.types.ts index 58996386d..844a540b6 100644 --- a/desktop/src/shared/types/sequencer.types.ts +++ b/desktop/src/shared/types/sequencer.types.ts @@ -1,6 +1,7 @@ import { AutoSubFolderMode, Camera, CameraCaptureEvent, CameraStartCapture, Dither } from './camera.types' import { Focuser } from './focuser.types' import { Mount } from './mount.types' +import { Rotator } from './rotator.types' import { FilterWheel } from './wheel.types' export type SequenceCaptureMode = 'FULLY' | 'INTERLEAVED' @@ -39,6 +40,7 @@ export interface SequencePlan { mount?: Mount wheel?: FilterWheel focuser?: Focuser + rotator?: Rotator } export const EMPTY_SEQUENCE_PLAN: SequencePlan = { diff --git a/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightDetectStars.kt b/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightDetectStars.kt index 4c3e58a40..db7b9a684 100644 --- a/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightDetectStars.kt +++ b/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightDetectStars.kt @@ -31,6 +31,8 @@ data class PixInsightDetectStars( @JvmField val stars: List = emptyList(), ) { + override fun toString() = "Output(success=$success, errorMessage=$errorMessage, stars=${stars.size})" + companion object { @JvmStatic val FAILED = Output()