From e33f5da08efdb5fb93b1ed11b2d5da404b4e03de Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 09:32:49 -0300 Subject: [PATCH 01/19] [api]: Remove timeshape dependency --- settings.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 90057d3a3..44067acf1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,7 +32,6 @@ dependencyResolutionManagement { library("apache-collections", "org.apache.commons:commons-collections4:4.4") library("apache-numbers-complex", "org.apache.commons:commons-numbers-complex:1.1") library("oshi", "com.github.oshi:oshi-core:6.6.0") - library("timeshape", "net.iakovlev:timeshape:2022g.17") library("jna", "net.java.dev.jna:jna:5.14.0") library("kotest-assertions-core", "io.kotest:kotest-assertions-core:5.9.0") library("kotest-runner-junit5", "io.kotest:kotest-runner-junit5:5.9.0") From a7a586aca50a85fc9f3f9db52978e593c800f8e9 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 10:29:08 -0300 Subject: [PATCH 02/19] [desktop]: Add zoom in/out button on Image --- desktop/src/app/image/image.component.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 3fc34e725..1e2192e42 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -448,9 +448,22 @@ export class ImageComponent implements AfterViewInit, OnDestroy { app.topMenu.push({ icon: 'mdi mdi-fullscreen', + label: 'Fullscreen', command: () => this.enterFullscreen(), }) + app.topMenu.push({ + icon: 'mdi mdi-minus', + label: 'Zoom Out', + command: () => this.zoomOut(), + }) + + app.topMenu.push({ + icon: 'mdi mdi-plus', + label: 'Zoom In', + command: () => this.zoomIn(), + }) + this.stretchShadow.subscribe(value => { this.stretch.shadow = value / 65536 }) From f4dac32372cef281d3396f2a1b3137638290ab87 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 11:15:45 -0300 Subject: [PATCH 03/19] [desktop]: Add Reset Zoom and Fit to Screen buttons on Image --- desktop/src/app/image/image.component.ts | 32 +++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 1e2192e42..9bcbdd226 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -464,6 +464,18 @@ export class ImageComponent implements AfterViewInit, OnDestroy { command: () => this.zoomIn(), }) + app.topMenu.push({ + icon: 'mdi mdi-numeric-0', + label: 'Reset Zoom', + command: () => this.resetZoom(false), + }) + + app.topMenu.push({ + icon: 'mdi mdi-fit-to-screen', + label: 'Fit to Screen', + command: () => this.resetZoom(true), + }) + this.stretchShadow.subscribe(value => { this.stretch.shadow = value / 65536 }) @@ -874,9 +886,23 @@ export class ImageComponent implements AfterViewInit, OnDestroy { this.panZoom.smoothZoomAbs(window.innerWidth / 2, window.innerHeight / 2, scale * 0.9) } - resetZoom() { - if (!this.panZoom) return - this.panZoom.smoothZoomAbs(window.innerWidth / 2, window.innerHeight / 2, 1.0) + center() { + const { width, height } = this.image.nativeElement.getBoundingClientRect() + this.panZoom?.moveTo(window.innerWidth / 2 - width / 2, (window.innerHeight - 42) / 2 - height / 2) + } + + resetZoom(fitToScreen: boolean = false, center: boolean = true) { + if (fitToScreen) { + const { width, height } = this.image.nativeElement + const factor = Math.min(window.innerWidth, window.innerHeight - 42) / Math.min(width, height) + this.panZoom?.smoothZoomAbs(window.innerWidth / 2, window.innerHeight / 2, factor) + } else { + this.panZoom?.smoothZoomAbs(window.innerWidth / 2, window.innerHeight / 2, 1.0) + } + + if (center) { + this.center() + } } async enterFullscreen() { From c5f3e9cb9f4c2b0066ecec72109c2fa64efad185 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 15:11:57 -0300 Subject: [PATCH 04/19] [api][desktop]: Add option to annotate with SIMBAD online service --- .../nebulosa/api/image/ImageController.kt | 3 +- .../kotlin/nebulosa/api/image/ImageService.kt | 27 +++++++++--------- desktop/src/app/image/image.component.html | 18 ++++++------ desktop/src/app/image/image.component.ts | 21 ++++++++------ desktop/src/shared/services/api.service.ts | 4 +-- desktop/src/shared/types/image.types.ts | 8 ++++++ .../kotlin/nebulosa/simbad/SimbadSearch.kt | 28 +++++++++---------- .../kotlin/nebulosa/simbad/SimbadService.kt | 2 +- 8 files changed, 63 insertions(+), 48 deletions(-) diff --git a/api/src/main/kotlin/nebulosa/api/image/ImageController.kt b/api/src/main/kotlin/nebulosa/api/image/ImageController.kt index fb5aba30b..63d9a58a6 100644 --- a/api/src/main/kotlin/nebulosa/api/image/ImageController.kt +++ b/api/src/main/kotlin/nebulosa/api/image/ImageController.kt @@ -45,8 +45,9 @@ class ImageController( @RequestParam(required = false, defaultValue = "true") starsAndDSOs: Boolean, @RequestParam(required = false, defaultValue = "false") minorPlanets: Boolean, @RequestParam(required = false, defaultValue = "12.0") minorPlanetMagLimit: Double, + @RequestParam(required = false, defaultValue = "false") useSimbad: Boolean, @LocationParam location: Location? = null, - ) = imageService.annotations(path, starsAndDSOs, minorPlanets, minorPlanetMagLimit, location) + ) = imageService.annotations(path, starsAndDSOs, minorPlanets, minorPlanetMagLimit, useSimbad, location) @GetMapping("coordinate-interpolation") fun coordinateInterpolation(@RequestParam path: Path): CoordinateInterpolation? { diff --git a/api/src/main/kotlin/nebulosa/api/image/ImageService.kt b/api/src/main/kotlin/nebulosa/api/image/ImageService.kt index dcdb72d73..59d7ce7da 100644 --- a/api/src/main/kotlin/nebulosa/api/image/ImageService.kt +++ b/api/src/main/kotlin/nebulosa/api/image/ImageService.kt @@ -15,12 +15,13 @@ import nebulosa.image.algorithms.computation.Statistics import nebulosa.image.algorithms.transformation.* import nebulosa.image.format.ImageModifier import nebulosa.indi.device.camera.Camera -import nebulosa.log.debug import nebulosa.log.loggerFor import nebulosa.math.* import nebulosa.nova.astrometry.VSOP87E import nebulosa.nova.position.Barycentric import nebulosa.sbd.SmallBodyDatabaseService +import nebulosa.simbad.SimbadSearch +import nebulosa.simbad.SimbadService import nebulosa.skycatalog.ClassificationType import nebulosa.skycatalog.SkyObjectType import nebulosa.star.detection.ImageStar @@ -50,6 +51,7 @@ class ImageService( private val calibrationFrameService: CalibrationFrameService, private val smallBodyDatabaseService: SmallBodyDatabaseService, private val simbadEntityRepository: SimbadEntityRepository, + private val simbadService: SimbadService, private val imageBucket: ImageBucket, private val threadPoolTaskExecutor: ThreadPoolTaskExecutor, private val connectionService: ConnectionService, @@ -168,7 +170,7 @@ class ImageService( fun annotations( path: Path, starsAndDSOs: Boolean, minorPlanets: Boolean, - minorPlanetMagLimit: Double = 12.0, + minorPlanetMagLimit: Double = 12.0, useSimbad: Boolean = false, location: Location? = null, ): List { val (image, calibration) = imageBucket[path] ?: return emptyList() @@ -229,27 +231,26 @@ class ImageService( if (starsAndDSOs) { threadPoolTaskExecutor.submitCompletable { - val barycentric = VSOP87E.EARTH.at(UTC(TimeYMDHMS(dateTime))) + LOG.info("finding star/DSO annotations. dateTime={}, useSimbad={}, calibration={}", dateTime, useSimbad, calibration) - LOG.info("finding star/DSO annotations. dateTime={}, calibration={}", dateTime, calibration) + val rightAscension = calibration.rightAscension + val declination = calibration.declination + val radius = calibration.radius - val catalog = simbadEntityRepository.find(null, null, calibration.rightAscension, calibration.declination, calibration.radius) + val catalog = if (useSimbad) { + simbadService.search(SimbadSearch.Builder().region(rightAscension, declination, radius).build()) + } else { + simbadEntityRepository.find(null, null, rightAscension, declination, radius) + } var count = 0 + val barycentric = VSOP87E.EARTH.at(UTC(TimeYMDHMS(dateTime))) for (entry in catalog) { if (entry.type == SkyObjectType.EXTRA_SOLAR_PLANET) continue val astrometric = barycentric.observe(entry).equatorial() - LOG.debug { - "%s: %s %s -> %s %s".format( - entry.name, - entry.rightAscensionJ2000.formatHMS(), entry.declinationJ2000.formatSignedDMS(), - astrometric.longitude.normalized.formatHMS(), astrometric.latitude.formatSignedDMS(), - ) - } - val (x, y) = wcs.skyToPix(astrometric.longitude.normalized, astrometric.latitude) val annotation = if (entry.type.classification == ClassificationType.STAR) ImageAnnotation(x, y, star = StarDSO(entry)) else ImageAnnotation(x, y, dso = StarDSO(entry)) diff --git a/desktop/src/app/image/image.component.html b/desktop/src/app/image/image.component.html index dc82b703d..ed6372920 100644 --- a/desktop/src/app/image/image.component.html +++ b/desktop/src/app/image/image.component.html @@ -50,18 +50,20 @@ -
-
- - +
- - + + +
+
+ - diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 9bcbdd226..4440b1f58 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -17,7 +17,7 @@ import { PreferenceService } from '../../shared/services/preference.service' import { PrimeService } from '../../shared/services/prime.service' import { CheckableMenuItem, ToggleableMenuItem } from '../../shared/types/app.types' import { Angle, AstronomicalObject, DeepSkyObject, EquatorialCoordinateJ2000, Star } from '../../shared/types/atlas.types' -import { DEFAULT_FOV, EMPTY_IMAGE_SOLVED, FOV, IMAGE_STATISTICS_BIT_OPTIONS, ImageAnnotation, ImageChannel, ImageData, ImageDetectStars, ImageFITSHeadersDialog, ImageFOVDialog, ImageInfo, ImageROI, ImageSCNRDialog, ImageSaveDialog, ImageSolved, ImageSolverDialog, ImageStatisticsBitOption, ImageStretchDialog, ImageTransformation, SCNR_PROTECTION_METHODS } from '../../shared/types/image.types' +import { DEFAULT_FOV, EMPTY_IMAGE_SOLVED, FOV, IMAGE_STATISTICS_BIT_OPTIONS, ImageAnnotation, ImageAnnotationDialog, ImageChannel, ImageData, ImageDetectStars, ImageFITSHeadersDialog, ImageFOVDialog, ImageInfo, ImageROI, ImageSCNRDialog, ImageSaveDialog, ImageSolved, ImageSolverDialog, ImageStatisticsBitOption, ImageStretchDialog, ImageTransformation, SCNR_PROTECTION_METHODS } from '../../shared/types/image.types' import { Mount } from '../../shared/types/mount.types' import { DEFAULT_SOLVER_TYPES } from '../../shared/types/settings.types' import { CoordinateInterpolator, InterpolatedCoordinate } from '../../shared/utils/coordinate-interpolation' @@ -87,10 +87,13 @@ export class ImageComponent implements AfterViewInit, OnDestroy { calibrationViaCamera = true - showAnnotationDialog = false - annotateWithStarsAndDSOs = true - annotateWithMinorPlanets = false - annotateWithMinorPlanetsMagLimit = 12.0 + readonly annotation: ImageAnnotationDialog = { + showDialog: false, + useStarsAndDSOs: true, + useMinorPlanets: false, + minorPlanetsMagLimit: 18.0, + useSimbad: false + } readonly solver: ImageSolverDialog = { showDialog: false, @@ -315,7 +318,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { toggleable: true, toggled: false, command: () => { - this.showAnnotationDialog = true + this.annotation.showDialog = true }, toggle: (event) => { event.originalEvent?.stopImmediatePropagation() @@ -799,12 +802,12 @@ export class ImageComponent implements AfterViewInit, OnDestroy { async annotateImage() { try { this.annotating = true - this.annotations = await this.api.annotationsOfImage(this.imageData.path!, - this.annotateWithStarsAndDSOs, this.annotateWithMinorPlanets, this.annotateWithMinorPlanetsMagLimit) + this.annotations = await this.api.annotationsOfImage(this.imageData.path!, this.annotation.useStarsAndDSOs, + this.annotation.useMinorPlanets, this.annotation.minorPlanetsMagLimit, this.annotation.useSimbad) this.annotationIsVisible = true this.annotationMenuItem.toggleable = this.annotations.length > 0 this.annotationMenuItem.toggled = this.annotationMenuItem.toggleable - this.showAnnotationDialog = false + this.annotation.showDialog = false } finally { this.annotating = false } diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts index 19f2ca01c..644ddc7d2 100644 --- a/desktop/src/shared/services/api.service.ts +++ b/desktop/src/shared/services/api.service.ts @@ -464,9 +464,9 @@ export class ApiService { annotationsOfImage( path: string, starsAndDSOs: boolean = true, minorPlanets: boolean = false, - minorPlanetMagLimit: number = 12.0, + minorPlanetMagLimit: number = 12.0, useSimbad: boolean = false, ) { - const query = this.http.query({ path, starsAndDSOs, minorPlanets, minorPlanetMagLimit, hasLocation: true }) + const query = this.http.query({ path, starsAndDSOs, minorPlanets, minorPlanetMagLimit, useSimbad, hasLocation: true }) return this.http.get(`image/annotations?${query}`) } diff --git a/desktop/src/shared/types/image.types.ts b/desktop/src/shared/types/image.types.ts index 8fe8c377b..e169ab0d7 100644 --- a/desktop/src/shared/types/image.types.ts +++ b/desktop/src/shared/types/image.types.ts @@ -259,3 +259,11 @@ export interface ImageTransformation { invert: boolean scnr: Pick } + +export interface ImageAnnotationDialog { + showDialog: boolean + useStarsAndDSOs: boolean + useMinorPlanets: boolean + minorPlanetsMagLimit: number + useSimbad: boolean +} diff --git a/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadSearch.kt b/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadSearch.kt index e18e288fb..cd328cdf7 100644 --- a/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadSearch.kt +++ b/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadSearch.kt @@ -8,20 +8,20 @@ import nebulosa.skycatalog.SkyObjectType @Suppress("ArrayInDataClass") data class SimbadSearch( - internal val id: Long = NO_ID, - internal val text: String? = null, - internal val rightAscension: Angle = 0.0, - internal val declination: Angle = 0.0, - internal val radius: Angle = 0.0, - internal val types: List? = null, - internal val magnitudeMin: Double = SkyObject.MAGNITUDE_MIN, - internal val magnitudeMax: Double = SkyObject.MAGNITUDE_MAX, - internal val constellation: Constellation? = null, - internal val ids: LongArray = LongArray(0), - internal val lastID: Long = NO_ID, - internal val limit: Int = SimbadService.DEFAULT_LIMIT, - internal val sortType: SortType = SortType.OID, - internal val sortDirection: SortDirection = SortDirection.ASCENDING, + @JvmField internal val id: Long = NO_ID, + @JvmField internal val text: String? = null, + @JvmField internal val rightAscension: Angle = 0.0, + @JvmField internal val declination: Angle = 0.0, + @JvmField internal val radius: Angle = 0.0, + @JvmField internal val types: List? = null, + @JvmField internal val magnitudeMin: Double = SkyObject.MAGNITUDE_MIN, + @JvmField internal val magnitudeMax: Double = SkyObject.MAGNITUDE_MAX, + @JvmField internal val constellation: Constellation? = null, + @JvmField internal val ids: LongArray = LongArray(0), + @JvmField internal val lastID: Long = NO_ID, + @JvmField internal val limit: Int = SimbadService.DEFAULT_LIMIT, + @JvmField internal val sortType: SortType = SortType.OID, + @JvmField internal val sortDirection: SortDirection = SortDirection.ASCENDING, ) { enum class SortType { diff --git a/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadService.kt b/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadService.kt index 6cbd5aed0..53d4fcfca 100644 --- a/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadService.kt +++ b/nebulosa-simbad/src/main/kotlin/nebulosa/simbad/SimbadService.kt @@ -94,7 +94,7 @@ class SimbadService( val constellation = SkyObject.constellationFor(rightAscensionJ2000, declinationJ2000) val entity = SimbadEntry( - id, name.joinToString("") { "[$it]" }, magnitude, + id, name.joinToString("|"), magnitude, rightAscensionJ2000, declinationJ2000, type, spType, majorAxis, minorAxis, orientation, pmRA, pmDEC, parallax.mas, radialVelocity, redshift, From 0bd193a8f91a7a2726a7b05ee397605f7ff94e15 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 16:07:29 -0300 Subject: [PATCH 05/19] [api]: Update list of INDI drivers --- .../nebulosa/indi/device/camera/Camera.kt | 13 ++-- .../indi/device/filterwheel/FilterWheel.kt | 14 +++++ .../nebulosa/indi/device/focuser/Focuser.kt | 19 +++++- .../kotlin/nebulosa/indi/device/gps/GPS.kt | 2 + .../nebulosa/indi/device/mount/Mount.kt | 10 ++- .../src/test/kotlin/INDIDriversLoader.kt | 62 +++++++++++++++++++ 6 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/camera/Camera.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/camera/Camera.kt index 3ec33dcb5..49e72e3b4 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/camera/Camera.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/camera/Camera.kt @@ -129,8 +129,8 @@ interface Camera : GuideOutput, Thermometer { "indi_altair_ccd", "indi_apogee_ccd", "indi_asi_ccd", - "indi_asi_single_ccd", "indi_atik_ccd", + "indi_bressercam_ccd", "indi_cam90_ccd", "indi_canon_ccd", "indi_dsi_ccd", @@ -138,22 +138,27 @@ interface Camera : GuideOutput, Thermometer { "indi_fishcamp_ccd", "indi_fli_ccd", "indi_fuji_ccd", + "indi_generic_ccd", "indi_gphoto_ccd", "indi_inovaplx_ccd", + "indi_kepler_ccd", + "indi_libcamera_ccd", "indi_mallincam_ccd", + "indi_meadecam_ccd", "indi_mi_ccd_eth", "indi_mi_ccd_usb", "indi_nightscape_ccd", "indi_nikon_ccd", "indi_nncam_ccd", + "indi_ogmacam_ccd", "indi_omegonprocam_ccd", "indi_orion_ssg3_ccd", - "indi_pentax_ccd", "indi_pentax", + "indi_pentax_ccd", "indi_playerone_ccd", + "indi_playerone_single_ccd", "indi_qhy_ccd", "indi_qsi_ccd", - "indi_rpicam", "indi_sbig_ccd", "indi_simulator_ccd", "indi_simulator_guide", @@ -162,9 +167,9 @@ interface Camera : GuideOutput, Thermometer { "indi_svbony_ccd", "indi_sx_ccd", "indi_toupcam_ccd", + "indi_tscam_ccd", "indi_v4l2_ccd", "indi_webcam_ccd", - "indi_kepler_ccd", ) } } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/filterwheel/FilterWheel.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/filterwheel/FilterWheel.kt index ff8eee139..90c27c12f 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/filterwheel/FilterWheel.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/filterwheel/FilterWheel.kt @@ -19,19 +19,33 @@ interface FilterWheel : Device { companion object { @JvmStatic val DRIVERS = setOf( + "indi_altair_wheel", "indi_apogee_wheel", "indi_asi_wheel", "indi_atik_wheel", + "indi_bressercam_wheel", "indi_fli_wheel", + "indi_mallincam_wheel", "indi_manual_wheel", + "indi_mi_sfw_eth", + "indi_mi_sfw_usb", + "indi_nncam_ccd", + "indi_oasis_filter_wheel", + "indi_ogmacam_wheel", + "indi_omegonprocam_wheel", "indi_optec_wheel", + "indi_pegasusindigo_wheel", + "indi_playerone_wheel", "indi_qhycfw1_wheel", "indi_qhycfw2_wheel", "indi_qhycfw3_wheel", "indi_quantum_wheel", "indi_simulator_wheel", + "indi_starshootg_wheel", "indi_sx_wheel", + "indi_toupcam_wheel", "indi_trutech_wheel", + "indi_tscam_wheel", "indi_xagyl_wheel", ) } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt index a18b1e2c9..f95b72e73 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt @@ -42,8 +42,12 @@ interface Focuser : Device, Thermometer { @JvmStatic val DRIVERS = setOf( "indi_aaf2_focus", "indi_activefocuser_focus", + "indi_alluna_tcs2", "indi_armadillo_focus", "indi_asi_focuser", + "indi_astromechfoc", + "indi_avalonud_focuser", + "indi_beefocus", "indi_celestron_sct_focus", "indi_deepskydad_af1_focus", "indi_deepskydad_af2_focus", @@ -53,11 +57,12 @@ interface Focuser : Device, Thermometer { "indi_efa_focus", "indi_esatto_focus", "indi_esattoarco_focus", + "indi_falcon_rotator", "indi_fcusb_focus", "indi_fli_focus", "indi_gemini_focus", "indi_hitecastrodc_focus", - "indi_integra_focus", + "indi_ieaf_focus", "indi_lacerta_mfoc_fmc_focus", "indi_lacerta_mfoc_focus", "indi_lakeside_focus", @@ -66,12 +71,20 @@ interface Focuser : Device, Thermometer { "indi_moonlite_focus", "indi_moonlitedro_focus", "indi_myfocuserpro2_focus", + "indi_nfocus", "indi_nightcrawler_focus", "indi_nstep_focus", + "indi_nstep_rotator", + "indi_oasis_focuser", "indi_onfocus_focus", "indi_pegasus_focuscube", + "indi_pegasus_focuscube3", + "indi_pegasus_prodigyMF", + "indi_pegasus_scopsoag", "indi_perfectstar_focus", "indi_platypus_focus", + "indi_pyxis_rotator", + "indi_qhy_focuser", "indi_rainbowrsf_focus", "indi_rbfocus_focus", "indi_robo_focus", @@ -79,6 +92,7 @@ interface Focuser : Device, Thermometer { "indi_sestosenso_focus", "indi_siefs_focus", "indi_simulator_focus", + "indi_simulator_rotator", "indi_smartfocus_focus", "indi_steeldrive2_focus", "indi_steeldrive_focus", @@ -86,6 +100,9 @@ interface Focuser : Device, Thermometer { "indi_tcfs_focus", "indi_teenastro_focus", "indi_usbfocusv3_focus", + "indi_wanderer_lite_rotator", + "indi_wanderer_rotator_lite_v2", + "indi_wanderer_rotator_mini", ) } } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/gps/GPS.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/gps/GPS.kt index 1f7bd73a5..4d2deb814 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/gps/GPS.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/gps/GPS.kt @@ -38,6 +38,8 @@ interface GPS : Device { } @JvmStatic val DRIVERS = setOf( + "indi_gpsd", + "indi_gpsnmea", "indi_simulator_gps", ) } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/mount/Mount.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/mount/Mount.kt index 1a4bc7d5e..847336c4e 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/mount/Mount.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/mount/Mount.kt @@ -78,7 +78,9 @@ interface Mount : GuideOutput, GPS, Parkable { companion object { @JvmStatic val DRIVERS = setOf( + "indi_ahpgt_telescope", "indi_astrotrac_telescope", + "indi_avalonud_telescope", "indi_azgti_telescope", "indi_bresserexos2", "indi_celestron_aux", @@ -94,12 +96,12 @@ interface Mount : GuideOutput, GPS, Parkable { "indi_lx200_10micron", "indi_lx200_16", "indi_lx200_OnStep", + "indi_lx200_OpenAstroTech", "indi_lx200_TeenAstro", + "indi_lx200_pegasus_nyx101", "indi_lx200am5", "indi_lx200aok", - "indi_lx200ap_gtocp2", "indi_lx200ap_v2", - "indi_lx200ap", "indi_lx200autostar", "indi_lx200basic", "indi_lx200classic", @@ -107,12 +109,12 @@ interface Mount : GuideOutput, GPS, Parkable { "indi_lx200gemini", "indi_lx200gotonova", "indi_lx200gps", - "indi_lx200_OpenAstroTech", "indi_lx200pulsar2", "indi_lx200ss2000pc", "indi_lx200stargo", "indi_lx200zeq25", "indi_paramount_telescope", + "indi_planewave_telescope", "indi_pmc8_telescope", "indi_rainbow_telescope", "indi_script_telescope", @@ -120,11 +122,13 @@ interface Mount : GuideOutput, GPS, Parkable { "indi_skycommander_telescope", "indi_skywatcherAltAzMount", "indi_staradventurer2i_telescope", + "indi_staradventurergti_telescope", "indi_starbook_telescope", "indi_starbook_ten", "indi_synscan_telescope", "indi_synscanlegacy_telescope", "indi_temma_telescope", + "shelyak_usis", ) } } diff --git a/nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt b/nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt new file mode 100644 index 000000000..480410195 --- /dev/null +++ b/nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt @@ -0,0 +1,62 @@ +import com.fasterxml.aalto.stax.InputFactoryImpl +import nebulosa.xml.attribute +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.InputStream +import java.io.SequenceInputStream +import java.util.* +import java.util.concurrent.TimeUnit +import javax.xml.stream.XMLStreamConstants + +object INDIDriversLoader { + + const val URL = "https://raw.githubusercontent.com/KDE/kstars/master/kstars/data/indidrivers.xml" + + @JvmStatic + fun main(args: Array) { + val request = Request.Builder().get().url(URL).build() + + HTTP_CLIENT.newCall(request).execute().use { response -> + val reader = with(Vector()) { + add("".byteInputStream()) + add(response.body!!.byteStream().also { it.skip(38) }) + add("".byteInputStream()) + val source = SequenceInputStream(elements()) + XML_INPUT_FACTORY.createXMLStreamReader(source) + } + + var group = "" + val devices = HashMap>(8) + + while (reader.hasNext()) { + when (reader.next()) { + XMLStreamConstants.START_ELEMENT -> { + when (reader.localName) { + "devGroup" -> group = reader.attribute("group")!!.uppercase() + "driver" -> devices.getOrPut(group, ::TreeSet).add(reader.elementText) + } + } + } + } + + for ((name, drivers) in devices) { + println(name) + + for (driver in drivers) { + println("\"$driver\",") + } + + println() + } + } + } + + @JvmStatic private val XML_INPUT_FACTORY = InputFactoryImpl() + + @JvmStatic private val HTTP_CLIENT = OkHttpClient.Builder() + .connectTimeout(5L, TimeUnit.MINUTES) + .writeTimeout(5L, TimeUnit.MINUTES) + .readTimeout(5L, TimeUnit.MINUTES) + .callTimeout(5L, TimeUnit.MINUTES) + .build() +} From 1a06e24f4a45a4dd277d82c067d28d1e1f0edf4b Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 17:59:10 -0300 Subject: [PATCH 06/19] [api]: Support Rotator --- .../api/connection/ConnectionService.kt | 14 ++-- .../kotlin/nebulosa/indi/client/INDIClient.kt | 6 ++ .../device/INDIDeviceProtocolHandler.kt | 66 ++++++++++++------- .../client/device/rotators/INDIRotator.kt | 53 +++++++++++++++ .../indi/device/AbstractINDIDeviceProvider.kt | 35 ++++++++-- .../indi/device/INDIDeviceProvider.kt | 25 +++---- .../nebulosa/indi/device/focuser/Focuser.kt | 18 +++-- .../nebulosa/indi/device/rotator/Rotator.kt | 50 +++++++++++++- .../indi/device/rotator/RotatorAttached.kt | 5 ++ .../indi/device/rotator/RotatorDetached.kt | 5 ++ .../indi/device/rotator/RotatorEvent.kt | 5 ++ .../src/test/kotlin/INDIDriversLoader.kt | 62 ----------------- 12 files changed, 225 insertions(+), 119 deletions(-) create mode 100644 nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAttached.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorDetached.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorEvent.kt delete mode 100644 nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt diff --git a/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt b/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt index 3fc34e50c..189cc630d 100644 --- a/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt +++ b/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt @@ -106,31 +106,31 @@ class ConnectionService( disconnectAll() } - fun cameras(id: String): List { + fun cameras(id: String): Collection { return providers[id]?.cameras() ?: emptyList() } - fun mounts(id: String): List { + fun mounts(id: String): Collection { return providers[id]?.mounts() ?: emptyList() } - fun focusers(id: String): List { + fun focusers(id: String): Collection { return providers[id]?.focusers() ?: emptyList() } - fun wheels(id: String): List { + fun wheels(id: String): Collection { return providers[id]?.wheels() ?: emptyList() } - fun gpss(id: String): List { + fun gpss(id: String): Collection { return providers[id]?.gps() ?: emptyList() } - fun guideOutputs(id: String): List { + fun guideOutputs(id: String): Collection { return providers[id]?.guideOutputs() ?: emptyList() } - fun thermometers(id: String): List { + fun thermometers(id: String): Collection { return providers[id]?.thermometers() ?: emptyList() } 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 81a68bb42..1c6199ac5 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 @@ -10,6 +10,7 @@ import nebulosa.indi.client.device.cameras.SVBonyCamera import nebulosa.indi.client.device.cameras.SimCamera import nebulosa.indi.client.device.focusers.INDIFocuser import nebulosa.indi.client.device.mounts.INDIMount +import nebulosa.indi.client.device.rotators.INDIRotator import nebulosa.indi.client.device.wheels.INDIFilterWheel import nebulosa.indi.device.Device import nebulosa.indi.device.INDIDeviceProvider @@ -18,6 +19,7 @@ import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import nebulosa.indi.protocol.GetProperties import nebulosa.indi.protocol.INDIProtocol import nebulosa.indi.protocol.io.INDIConnection @@ -60,6 +62,10 @@ data class INDIClient(val connection: INDIConnection) : INDIDeviceProtocolHandle return INDIFilterWheel(this, message.device) } + override fun newRotator(message: INDIProtocol): Rotator { + return INDIRotator(this, message.device) + } + override fun newGPS(message: INDIProtocol): GPS { return GPSDevice(this, message.device) } 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 6d57d01c5..f5ffc53bb 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 @@ -9,6 +9,7 @@ import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import nebulosa.indi.protocol.DefTextVector import nebulosa.indi.protocol.DelProperty import nebulosa.indi.protocol.INDIProtocol @@ -38,6 +39,8 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message protected abstract fun newFilterWheel(message: INDIProtocol): FilterWheel + protected abstract fun newRotator(message: INDIProtocol): Rotator + protected abstract fun newGPS(message: INDIProtocol): GPS open fun start() { @@ -64,6 +67,20 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message } } + private fun takeMessageFromReorderingQueue(device: Device) { + if (messageReorderingQueue.isNotEmpty()) { + repeat(messageReorderingQueue.size) { + val queuedMessage = messageReorderingQueue.take() + + if (queuedMessage.device == device.name) { + handleMessage(queuedMessage) + } else { + messageReorderingQueue.offer(queuedMessage) + } + } + } + } + @Synchronized override fun handleMessage(message: INDIProtocol) { if (message.device in notRegisteredDevices) return @@ -75,26 +92,13 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message var registered = false - fun takeMessageFromReorderingQueue(device: Device) { - if (messageReorderingQueue.isNotEmpty()) { - repeat(messageReorderingQueue.size) { - val queuedMessage = messageReorderingQueue.take() - - if (queuedMessage.device == device.name) { - handleMessage(queuedMessage) - } else { - messageReorderingQueue.offer(message) - } - } - } - } - if (executable in Camera.DRIVERS) { registered = true with(newCamera(message, executable)) { - registerCamera(this) - takeMessageFromReorderingQueue(this) + if (registerCamera(this)) { + takeMessageFromReorderingQueue(this) + } } } @@ -102,8 +106,9 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message registered = true with(newMount(message, executable)) { - registerMount(this) - takeMessageFromReorderingQueue(this) + if (registerMount(this)) { + takeMessageFromReorderingQueue(this) + } } } @@ -111,8 +116,9 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message registered = true with(newFilterWheel(message)) { - registerFilterWheel(this) - takeMessageFromReorderingQueue(this) + if (registerFilterWheel(this)) { + takeMessageFromReorderingQueue(this) + } } } @@ -120,8 +126,19 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message registered = true with(newFocuser(message)) { - registerFocuser(this) - takeMessageFromReorderingQueue(this) + if (registerFocuser(this)) { + takeMessageFromReorderingQueue(this) + } + } + } + + if (executable in Rotator.DRIVERS) { + registered = true + + with(newRotator(message)) { + if (registerRotator(this)) { + takeMessageFromReorderingQueue(this) + } } } @@ -129,8 +146,9 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message registered = true with(newGPS(message)) { - registerGPS(this) - takeMessageFromReorderingQueue(this) + if (registerGPS(this)) { + takeMessageFromReorderingQueue(this) + } } } diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt new file mode 100644 index 000000000..0a844c8f2 --- /dev/null +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt @@ -0,0 +1,53 @@ +package nebulosa.indi.client.device.rotators + +import nebulosa.indi.client.INDIClient +import nebulosa.indi.client.device.INDIDevice +import nebulosa.indi.device.rotator.Rotator +import nebulosa.indi.protocol.INDIProtocol + +// https://github.com/indilib/indi/blob/master/libs/indibase/indirotator.cpp + +internal open class INDIRotator( + override val sender: INDIClient, + override val name: String, +) : INDIDevice(), Rotator { + + @Volatile final override var canAbort = false + @Volatile final override var canHome = false + @Volatile final override var canSync = false + @Volatile final override var canReverse = false + @Volatile final override var hasBacklashCompensation = false + @Volatile final override var backslash = 0 + @Volatile final override var minAngle = 0.0 + @Volatile final override var maxAngle = 0.0 + + override fun moveRotator(angle: Double) { + TODO("Not yet implemented") + } + + override fun syncRotator(angle: Double) { + TODO("Not yet implemented") + } + + override fun homeRotator() { + TODO("Not yet implemented") + } + + override fun reverseRotator() { + TODO("Not yet implemented") + } + + override fun abortRotator() { + TODO("Not yet implemented") + } + + override fun handleMessage(message: INDIProtocol) { + super.handleMessage(message) + } + + override fun close() = Unit + + override fun toString() = "INDIRotator(name=$name, canAbort=$canAbort, canHome=$canHome, " + + "canSync=$canSync, canReverse=$canReverse, hasBacklashCompensation=$hasBacklashCompensation, " + + "backslash=$backslash, minAngle=$minAngle, maxAngle=$maxAngle)" +} 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 c5dd76f8c..22b8c030b 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 @@ -19,6 +19,9 @@ import nebulosa.indi.device.guide.GuideOutputDetached import nebulosa.indi.device.mount.Mount import nebulosa.indi.device.mount.MountAttached import nebulosa.indi.device.mount.MountDetached +import nebulosa.indi.device.rotator.Rotator +import nebulosa.indi.device.rotator.RotatorAttached +import nebulosa.indi.device.rotator.RotatorDetached import nebulosa.indi.device.thermometer.Thermometer import nebulosa.indi.device.thermometer.ThermometerAttached import nebulosa.indi.device.thermometer.ThermometerDetached @@ -32,6 +35,7 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { private val mounts = HashMap(2) private val wheels = HashMap(2) private val focusers = HashMap(2) + private val rotators = HashMap(2) private val gps = HashMap(2) private val guideOutputs = HashMap(2) private val thermometers = HashMap(2) @@ -44,31 +48,35 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { fun fireOnConnectionClosed() = handlers.forEach { it.onConnectionClosed() } - override fun cameras() = cameras.values.toList() + override fun cameras() = cameras.values override fun camera(id: String) = cameras[id] ?: cameras.values.find { it.name == id } - override fun mounts() = mounts.values.toList() + override fun mounts() = mounts.values override fun mount(id: String) = mounts[id] ?: mounts.values.find { it.name == id } - override fun focusers() = focusers.values.toList() + override fun focusers() = focusers.values override fun focuser(id: String) = focusers[id] ?: focusers.values.find { it.name == id } - override fun wheels() = wheels.values.toList() + override fun wheels() = wheels.values override fun wheel(id: String) = wheels[id] ?: wheels.values.find { it.name == id } - override fun gps() = gps.values.toList() + override fun rotators() = rotators.values + + override fun rotator(id: String) = rotators[id] ?: rotators.values.find { it.name == id } + + override fun gps() = gps.values override fun gps(id: String) = gps[id] ?: gps.values.find { it.name == id } - override fun guideOutputs() = guideOutputs.values.toList() + override fun guideOutputs() = guideOutputs.values override fun guideOutput(id: String) = guideOutputs[id] ?: guideOutputs.values.find { it.name == id } - override fun thermometers() = thermometers.values.toList() + override fun thermometers() = thermometers.values override fun thermometer(id: String) = thermometers[id] ?: thermometers.values.find { it.name == id } @@ -111,6 +119,19 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { LOG.info("focuser detached: {} ({})", device.name, device.id) } + fun registerRotator(device: Rotator): Boolean { + if (device.id in rotators) return false + rotators[device.id] = device + fireOnEventReceived(RotatorAttached(device)) + LOG.info("rotator attached: {} ({})", device.name, device.id) + return true + } + + fun unregisterRotator(device: Rotator) { + fireOnEventReceived(RotatorDetached(rotators.remove(device.id) ?: return)) + LOG.info("rotator detached: {} ({})", device.name, device.id) + } + fun registerFilterWheel(device: FilterWheel): Boolean { if (device.id in wheels) return false wheels[device.id] = device 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 7264a009a..e8a23d0a0 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 @@ -6,6 +6,7 @@ import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS import nebulosa.indi.device.guide.GuideOutput import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import nebulosa.indi.device.thermometer.Thermometer import java.io.Closeable @@ -15,36 +16,38 @@ interface INDIDeviceProvider : MessageSender, Closeable { fun unregisterDeviceEventHandler(handler: DeviceEventHandler): Boolean - fun device(id: String): Device? { - return camera(id) ?: mount(id) ?: focuser(id) ?: wheel(id) - ?: gps(id) ?: guideOutput(id) ?: thermometer(id) - } + fun device(id: String) = camera(id) ?: mount(id) ?: focuser(id) ?: wheel(id) + ?: rotator(id) ?: gps(id) ?: guideOutput(id) ?: thermometer(id) - fun cameras(): List + fun cameras(): Collection fun camera(id: String): Camera? - fun mounts(): List + fun mounts(): Collection fun mount(id: String): Mount? - fun focusers(): List + fun focusers(): Collection fun focuser(id: String): Focuser? - fun wheels(): List + fun wheels(): Collection fun wheel(id: String): FilterWheel? - fun gps(): List + fun rotators(): Collection + + fun rotator(id: String): Rotator? + + fun gps(): Collection fun gps(id: String): GPS? - fun guideOutputs(): List + fun guideOutputs(): Collection fun guideOutput(id: String): GuideOutput? - fun thermometers(): List + fun thermometers(): Collection fun thermometer(id: String): Thermometer? } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt index f95b72e73..42fd11d87 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt @@ -39,15 +39,20 @@ interface Focuser : Device, Thermometer { companion object { + // grep -irl --include \*.h "public INDI::Focuser" . @JvmStatic val DRIVERS = setOf( "indi_aaf2_focus", "indi_activefocuser_focus", "indi_alluna_tcs2", "indi_armadillo_focus", "indi_asi_focuser", + "indi_astrolink4", + "indi_astrolink4mini2", "indi_astromechfoc", "indi_avalonud_focuser", "indi_beefocus", + "indi_celestron_aux", + "indi_celestron_gps", "indi_celestron_sct_focus", "indi_deepskydad_af1_focus", "indi_deepskydad_af2_focus", @@ -57,15 +62,17 @@ interface Focuser : Device, Thermometer { "indi_efa_focus", "indi_esatto_focus", "indi_esattoarco_focus", - "indi_falcon_rotator", "indi_fcusb_focus", "indi_fli_focus", "indi_gemini_focus", + "indi_gphoto_ccd", "indi_hitecastrodc_focus", "indi_ieaf_focus", "indi_lacerta_mfoc_fmc_focus", "indi_lacerta_mfoc_focus", "indi_lakeside_focus", + "indi_lx200generic", + "indi_lx200stargo", "indi_lynx_focus", "indi_microtouch_focus", "indi_moonlite_focus", @@ -74,16 +81,16 @@ interface Focuser : Device, Thermometer { "indi_nfocus", "indi_nightcrawler_focus", "indi_nstep_focus", - "indi_nstep_rotator", "indi_oasis_focuser", "indi_onfocus_focus", "indi_pegasus_focuscube", "indi_pegasus_focuscube3", + "indi_pegasus_ppba", "indi_pegasus_prodigyMF", "indi_pegasus_scopsoag", + "indi_pegasus_upb", "indi_perfectstar_focus", "indi_platypus_focus", - "indi_pyxis_rotator", "indi_qhy_focuser", "indi_rainbowrsf_focus", "indi_rbfocus_focus", @@ -92,7 +99,6 @@ interface Focuser : Device, Thermometer { "indi_sestosenso_focus", "indi_siefs_focus", "indi_simulator_focus", - "indi_simulator_rotator", "indi_smartfocus_focus", "indi_steeldrive2_focus", "indi_steeldrive_focus", @@ -100,9 +106,7 @@ interface Focuser : Device, Thermometer { "indi_tcfs_focus", "indi_teenastro_focus", "indi_usbfocusv3_focus", - "indi_wanderer_lite_rotator", - "indi_wanderer_rotator_lite_v2", - "indi_wanderer_rotator_mini", + "indilx200", ) } } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt index 885b46421..f358151c9 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt @@ -2,4 +2,52 @@ package nebulosa.indi.device.rotator import nebulosa.indi.device.Device -interface Rotator : Device +interface Rotator : Device { + + val canAbort: Boolean + + val canHome: Boolean + + val canSync: Boolean + + val canReverse: Boolean + + val hasBacklashCompensation: Boolean + + val backslash: Int + + val minAngle: Double + + val maxAngle: Double + + fun moveRotator(angle: Double) + + fun syncRotator(angle: Double) + + fun homeRotator() + + fun reverseRotator() + + fun abortRotator() + + companion object { + + @JvmStatic val DRIVERS = setOf( + "indi_deepskydad_fr1", + "indi_esattoarco_focus", + "indi_falcon_rotator", + "indi_gemini_focus", + "indi_integra_focus", + "indi_lx200generic", + "indi_nframe_rotator", + "indi_nightcrawler_focus", + "indi_nstep_rotator", + "indi_pyxis_rotator", + "indi_seletek_rotator", + "indi_simulator_rotator", + "indi_wanderer_lite_rotator", + "indi_wanderer_rotator_lite_v2", + "indi_wanderer_rotator_mini", + ) + } +} diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAttached.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAttached.kt new file mode 100644 index 000000000..253383036 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAttached.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.DeviceAttached + +data class RotatorAttached(override val device: Rotator) : RotatorEvent, DeviceAttached diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorDetached.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorDetached.kt new file mode 100644 index 000000000..aed1ebce5 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorDetached.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.DeviceDetached + +data class RotatorDetached(override val device: Rotator) : RotatorEvent, DeviceDetached diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorEvent.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorEvent.kt new file mode 100644 index 000000000..15e721351 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorEvent.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.DeviceEvent + +interface RotatorEvent : DeviceEvent diff --git a/nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt b/nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt deleted file mode 100644 index 480410195..000000000 --- a/nebulosa-indi-device/src/test/kotlin/INDIDriversLoader.kt +++ /dev/null @@ -1,62 +0,0 @@ -import com.fasterxml.aalto.stax.InputFactoryImpl -import nebulosa.xml.attribute -import okhttp3.OkHttpClient -import okhttp3.Request -import java.io.InputStream -import java.io.SequenceInputStream -import java.util.* -import java.util.concurrent.TimeUnit -import javax.xml.stream.XMLStreamConstants - -object INDIDriversLoader { - - const val URL = "https://raw.githubusercontent.com/KDE/kstars/master/kstars/data/indidrivers.xml" - - @JvmStatic - fun main(args: Array) { - val request = Request.Builder().get().url(URL).build() - - HTTP_CLIENT.newCall(request).execute().use { response -> - val reader = with(Vector()) { - add("".byteInputStream()) - add(response.body!!.byteStream().also { it.skip(38) }) - add("".byteInputStream()) - val source = SequenceInputStream(elements()) - XML_INPUT_FACTORY.createXMLStreamReader(source) - } - - var group = "" - val devices = HashMap>(8) - - while (reader.hasNext()) { - when (reader.next()) { - XMLStreamConstants.START_ELEMENT -> { - when (reader.localName) { - "devGroup" -> group = reader.attribute("group")!!.uppercase() - "driver" -> devices.getOrPut(group, ::TreeSet).add(reader.elementText) - } - } - } - } - - for ((name, drivers) in devices) { - println(name) - - for (driver in drivers) { - println("\"$driver\",") - } - - println() - } - } - } - - @JvmStatic private val XML_INPUT_FACTORY = InputFactoryImpl() - - @JvmStatic private val HTTP_CLIENT = OkHttpClient.Builder() - .connectTimeout(5L, TimeUnit.MINUTES) - .writeTimeout(5L, TimeUnit.MINUTES) - .readTimeout(5L, TimeUnit.MINUTES) - .callTimeout(5L, TimeUnit.MINUTES) - .build() -} From 5e52b605d152f36d6bd62e98957d721933f872f3 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 18:33:22 -0300 Subject: [PATCH 07/19] [api]: Support Rotator --- .../api/connection/ConnectionService.kt | 18 +++++ .../api/rotators/RotatorController.kt | 56 +++++++++++++ .../api/rotators/RotatorDeserializer.kt | 16 ++++ .../api/rotators/RotatorSerializer.kt | 29 +++++++ .../nebulosa/api/rotators/RotatorService.kt | 32 ++++++++ .../device/INDIDeviceProtocolHandler.kt | 1 + .../client/device/rotators/INDIRotator.kt | 78 ++++++++++++++++--- .../nebulosa/indi/device/focuser/Focuser.kt | 2 +- .../nebulosa/indi/device/rotator/Rotator.kt | 5 +- .../device/rotator/RotatorAngleChanged.kt | 5 ++ .../device/rotator/RotatorCanAbortChanged.kt | 5 ++ .../device/rotator/RotatorCanHomeChanged.kt | 5 ++ .../rotator/RotatorCanReverseChanged.kt | 5 ++ .../device/rotator/RotatorCanSyncChanged.kt | 5 ++ 14 files changed, 248 insertions(+), 14 deletions(-) create mode 100644 api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt create mode 100644 api/src/main/kotlin/nebulosa/api/rotators/RotatorDeserializer.kt create mode 100644 api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt create mode 100644 api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAngleChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanAbortChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanHomeChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanReverseChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanSyncChanged.kt diff --git a/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt b/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt index 189cc630d..55d135d77 100644 --- a/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt +++ b/api/src/main/kotlin/nebulosa/api/connection/ConnectionService.kt @@ -13,6 +13,7 @@ import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.gps.GPS import nebulosa.indi.device.guide.GuideOutput import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import nebulosa.indi.device.thermometer.Thermometer import nebulosa.log.error import nebulosa.log.loggerFor @@ -122,6 +123,10 @@ class ConnectionService( return providers[id]?.wheels() ?: emptyList() } + fun rotators(id: String): Collection { + return providers[id]?.rotators() ?: emptyList() + } + fun gpss(id: String): Collection { return providers[id]?.gps() ?: emptyList() } @@ -150,6 +155,10 @@ class ConnectionService( return providers.values.flatMap { it.wheels() } } + fun rotators(): List { + return providers.values.flatMap { it.rotators() } + } + fun gpss(): List { return providers.values.flatMap { it.gps() } } @@ -178,6 +187,10 @@ class ConnectionService( return providers[id]?.wheel(name) } + fun rotator(id: String, name: String): Rotator? { + return providers[id]?.rotator(name) + } + fun gps(id: String, name: String): GPS? { return providers[id]?.gps(name) } @@ -206,6 +219,10 @@ class ConnectionService( return providers.firstNotNullOfOrNull { it.value.wheel(name) } } + fun rotator(name: String): Rotator? { + return providers.firstNotNullOfOrNull { it.value.rotator(name) } + } + fun gps(name: String): GPS? { return providers.firstNotNullOfOrNull { it.value.gps(name) } } @@ -223,6 +240,7 @@ class ConnectionService( ?: mount(name) ?: focuser(name) ?: wheel(name) + ?: rotator(name) ?: guideOutput(name) ?: gps(name) ?: thermometer(name) diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt new file mode 100644 index 000000000..719d4f861 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt @@ -0,0 +1,56 @@ +package nebulosa.api.rotators + +import jakarta.validation.Valid +import nebulosa.api.connection.ConnectionService +import nebulosa.indi.device.rotator.Rotator +import org.hibernate.validator.constraints.Range +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("rotators") +class RotatorController( + private val connectionService: ConnectionService, + private val rotatorService: RotatorService, +) { + + @GetMapping + fun rotators(): List { + return connectionService.rotators().sorted() + } + + @GetMapping("{rotator}") + fun rotator(rotator: Rotator): Rotator { + return rotator + } + + @PutMapping("{rotator}/connect") + fun connect(rotator: Rotator) { + rotatorService.connect(rotator) + } + + @PutMapping("{rotator}/disconnect") + fun disconnect(rotator: Rotator) { + rotatorService.disconnect(rotator) + } + + @PutMapping("{rotator}/move") + fun moveIn( + rotator: Rotator, + @RequestParam @Valid @Range(min = 0, max = 360) angle: Double, + ) { + rotatorService.move(rotator, angle) + } + + @PutMapping("{rotator}/abort") + fun abort(rotator: Rotator) { + rotatorService.abort(rotator) + } + + @PutMapping("{rotator}/sync") + fun sync( + rotator: Rotator, + @RequestParam @Valid @Range(min = 0, max = 360) angle: Double, + ) { + rotatorService.sync(rotator, angle) + } +} diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorDeserializer.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorDeserializer.kt new file mode 100644 index 000000000..8251893c3 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorDeserializer.kt @@ -0,0 +1,16 @@ +package nebulosa.api.rotators + +import nebulosa.api.beans.converters.device.DeviceDeserializer +import nebulosa.api.connection.ConnectionService +import nebulosa.indi.device.rotator.Rotator +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Lazy +import org.springframework.stereotype.Component + +@Component +class RotatorDeserializer : DeviceDeserializer(Rotator::class.java) { + + @Autowired @Lazy private lateinit var connectionService: ConnectionService + + override fun deviceFor(name: String) = connectionService.rotator(name) +} diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt new file mode 100644 index 000000000..f478f2f6e --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt @@ -0,0 +1,29 @@ +package nebulosa.api.rotators + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import nebulosa.indi.device.rotator.Rotator +import org.springframework.stereotype.Component + +@Component +class RotatorSerializer : StdSerializer(Rotator::class.java) { + + override fun serialize(value: Rotator, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeStartObject() + gen.writeStringField("sender", value.sender.id) + gen.writeStringField("id", value.id) + gen.writeStringField("name", value.name) + gen.writeBooleanField("connected", value.connected) + // gen.writeBooleanField("moving", value.moving) + gen.writeNumberField("angle", value.angle) + gen.writeBooleanField("canAbort", value.canAbort) + gen.writeBooleanField("canReverse", value.canReverse) + gen.writeBooleanField("canHome", value.canHome) + gen.writeBooleanField("canSync", value.canSync) + gen.writeBooleanField("hasBacklashCompensation", value.hasBacklashCompensation) + gen.writeNumberField("maxAngle", value.maxAngle) + gen.writeNumberField("minAngle", value.minAngle) + gen.writeEndObject() + } +} diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt new file mode 100644 index 000000000..9a421378e --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt @@ -0,0 +1,32 @@ +package nebulosa.api.rotators + +import nebulosa.indi.device.rotator.Rotator +import org.springframework.stereotype.Service + +@Service +class RotatorService { + + fun connect(rotator: Rotator) { + rotator.connect() + } + + fun disconnect(rotator: Rotator) { + rotator.disconnect() + } + + fun move(rotator: Rotator, angle: Double) { + rotator.moveRotator(angle) + } + + fun abort(rotator: Rotator) { + rotator.abortRotator() + } + + fun sync(rotator: Rotator, angle: Double) { + rotator.syncRotator(angle) + } + + fun home(rotator: Rotator) { + rotator.homeRotator() + } +} 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 f5ffc53bb..333c7fb28 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 @@ -188,6 +188,7 @@ abstract class INDIDeviceProtocolHandler : AbstractINDIDeviceProvider(), Message is Mount -> unregisterMount(device) is FilterWheel -> unregisterFilterWheel(device) is Focuser -> unregisterFocuser(device) + is Rotator -> unregisterRotator(device) is GPS -> unregisterGPS(device) } diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt index 0a844c8f2..dda3b206d 100644 --- a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt @@ -2,10 +2,13 @@ package nebulosa.indi.client.device.rotators import nebulosa.indi.client.INDIClient import nebulosa.indi.client.device.INDIDevice -import nebulosa.indi.device.rotator.Rotator +import nebulosa.indi.device.rotator.* +import nebulosa.indi.protocol.DefNumberVector +import nebulosa.indi.protocol.DefSwitchVector import nebulosa.indi.protocol.INDIProtocol +import nebulosa.indi.protocol.SetNumberVector -// https://github.com/indilib/indi/blob/master/libs/indibase/indirotator.cpp +// https://github.com/indilib/indi/blob/master/libs/indibase/indirotatorinterface.cpp internal open class INDIRotator( override val sender: INDIClient, @@ -18,31 +21,82 @@ internal open class INDIRotator( @Volatile final override var canReverse = false @Volatile final override var hasBacklashCompensation = false @Volatile final override var backslash = 0 + @Volatile final override var angle = 0.0 @Volatile final override var minAngle = 0.0 @Volatile final override var maxAngle = 0.0 + override fun handleMessage(message: INDIProtocol) { + when (message) { + is DefNumberVector -> { + when (message.name) { + "ROTATOR_LIMITS_VALUE" -> { + + } + } + } + is DefSwitchVector -> { + when (message.name) { + "ABORT" -> { + if ("ABS_ROTATOR_ANGLE" in message) { + canAbort = true + sender.fireOnEventReceived(RotatorCanAbortChanged(this)) + } + + if ("SYNC_ROTATOR_ANGLE" in message) { + canSync = true + sender.fireOnEventReceived(RotatorCanSyncChanged(this)) + } + } + "HOME" -> { + canHome = true + sender.fireOnEventReceived(RotatorCanHomeChanged(this)) + } + "ROTATOR_REVERSE" -> { + canReverse = true + sender.fireOnEventReceived(RotatorCanReverseChanged(this)) + } + } + } + is SetNumberVector -> { + when (message.name) { + "ANGLE" -> { + angle = (message["ABS_ROTATOR_ANGLE"] ?: return).value + sender.fireOnEventReceived(RotatorAngleChanged(this)) + } + } + } + else -> Unit + } + + super.handleMessage(message) + } + override fun moveRotator(angle: Double) { - TODO("Not yet implemented") + sendNewNumber("ANGLE", "ABS_ROTATOR_ANGLE" to angle) } override fun syncRotator(angle: Double) { - TODO("Not yet implemented") + if (canSync) { + sendNewNumber("ANGLE", "SYNC_ROTATOR_ANGLE" to angle) + } } override fun homeRotator() { - TODO("Not yet implemented") + if (canHome) { + sendNewSwitch("HOME", "ROTATOR_HOME" to true) + } } - override fun reverseRotator() { - TODO("Not yet implemented") + override fun reverseRotator(enable: Boolean) { + if (canReverse) { + sendNewSwitch("ROTATOR_REVERSE", (if (enable) "INDI_ENABLED" else "INDI_DISABLED") to true) + } } override fun abortRotator() { - TODO("Not yet implemented") - } - - override fun handleMessage(message: INDIProtocol) { - super.handleMessage(message) + if (canAbort) { + sendNewSwitch("ABORT", "ROTATOR_ABORT_MOTION" to true) + } } override fun close() = Unit diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt index 42fd11d87..920702192 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/focuser/Focuser.kt @@ -39,7 +39,7 @@ interface Focuser : Device, Thermometer { companion object { - // grep -irl --include \*.h "public INDI::Focuser" . + // grep -rl --include \*.h "public INDI::Focuser" . @JvmStatic val DRIVERS = setOf( "indi_aaf2_focus", "indi_activefocuser_focus", diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt index f358151c9..47bdb6fb4 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt @@ -16,6 +16,8 @@ interface Rotator : Device { val backslash: Int + val angle: Double + val minAngle: Double val maxAngle: Double @@ -26,12 +28,13 @@ interface Rotator : Device { fun homeRotator() - fun reverseRotator() + fun reverseRotator(enable: Boolean) fun abortRotator() companion object { + // grep -rl --include \*.h "public INDI::Rotator" . @JvmStatic val DRIVERS = setOf( "indi_deepskydad_fr1", "indi_esattoarco_focus", diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAngleChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAngleChanged.kt new file mode 100644 index 000000000..4a2be1aa1 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorAngleChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorAngleChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanAbortChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanAbortChanged.kt new file mode 100644 index 000000000..97ce3a0b2 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanAbortChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorCanAbortChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanHomeChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanHomeChanged.kt new file mode 100644 index 000000000..2e6b47de6 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanHomeChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorCanHomeChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanReverseChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanReverseChanged.kt new file mode 100644 index 000000000..d91be4e26 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanReverseChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorCanReverseChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanSyncChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanSyncChanged.kt new file mode 100644 index 000000000..e055be879 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorCanSyncChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorCanSyncChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent From f3140808e6afea83686692c55f0d7623f65f49c9 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Sun, 19 May 2024 23:21:26 -0300 Subject: [PATCH 08/19] [desktop]: Use PreferenceService for Focuser preference --- desktop/src/app/focuser/focuser.component.ts | 20 +++++++++---------- .../src/shared/services/preference.service.ts | 6 +++++- desktop/src/shared/types/focuser.types.ts | 4 ---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/desktop/src/app/focuser/focuser.component.ts b/desktop/src/app/focuser/focuser.component.ts index be589d0dd..c474977d1 100644 --- a/desktop/src/app/focuser/focuser.component.ts +++ b/desktop/src/app/focuser/focuser.component.ts @@ -3,8 +3,8 @@ import { ActivatedRoute } from '@angular/router' import hotkeys from 'hotkeys-js' import { ApiService } from '../../shared/services/api.service' import { ElectronService } from '../../shared/services/electron.service' -import { LocalStorageService } from '../../shared/services/local-storage.service' -import { EMPTY_FOCUSER, Focuser, FocuserPreference, focuserPreferenceKey } from '../../shared/types/focuser.types' +import { PreferenceService } from '../../shared/services/preference.service' +import { EMPTY_FOCUSER, Focuser } from '../../shared/types/focuser.types' import { AppComponent } from '../app.component' @Component({ @@ -35,8 +35,8 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { constructor( private app: AppComponent, private api: ApiService, - private electron: ElectronService, - private storage: LocalStorageService, + electron: ElectronService, + private preference: PreferenceService, private route: ActivatedRoute, ngZone: NgZone, ) { @@ -165,7 +165,7 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { private loadPreference() { if (this.focuser.id) { - const preference = this.storage.get(focuserPreferenceKey(this.focuser), {}) + const preference = this.preference.focuserPreference(this.focuser).get() this.stepsRelative = preference.stepsRelative ?? 100 this.stepsAbsolute = preference.stepsAbsolute ?? this.focuser.position } @@ -173,12 +173,10 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { private savePreference() { if (this.focuser.connected) { - const preference: FocuserPreference = { - stepsRelative: this.stepsRelative, - stepsAbsolute: this.stepsAbsolute, - } - - this.storage.set(focuserPreferenceKey(this.focuser), preference) + const preference = this.preference.focuserPreference(this.focuser).get() + preference.stepsAbsolute = this.stepsAbsolute + preference.stepsRelative = this.stepsRelative + this.preference.focuserPreference(this.focuser).set(preference) } } } \ No newline at end of file diff --git a/desktop/src/shared/services/preference.service.ts b/desktop/src/shared/services/preference.service.ts index 8416fc39b..8670ff6a0 100644 --- a/desktop/src/shared/services/preference.service.ts +++ b/desktop/src/shared/services/preference.service.ts @@ -5,7 +5,7 @@ import { EMPTY_LOCATION, Location } from '../types/atlas.types' import { CalibrationPreference } from '../types/calibration.types' import { Camera, CameraPreference, CameraStartCapture, EMPTY_CAMERA_PREFERENCE } from '../types/camera.types' import { Device } from '../types/device.types' -import { Focuser } from '../types/focuser.types' +import { Focuser, FocuserPreference } from '../types/focuser.types' import { ConnectionDetails, Equipment, HomePreference } from '../types/home.types' import { EMPTY_IMAGE_PREFERENCE, FOV, ImagePreference } from '../types/image.types' import { EMPTY_PLATE_SOLVER_PREFERENCE, PlateSolverPreference, PlateSolverType } from '../types/settings.types' @@ -74,6 +74,10 @@ export class PreferenceService { return new PreferenceData(this.storage, `focusOffset.${wheel.name}.${position}.${focuser.name}`, () => 0) } + focuserPreference(focuser: Focuser) { + return new PreferenceData(this.storage, `focuser.${focuser.name}`, {}) + } + readonly connections = new PreferenceData(this.storage, 'home.connections', () => []) readonly locations = new PreferenceData(this.storage, 'locations', () => [structuredClone(EMPTY_LOCATION)]) readonly selectedLocation = new PreferenceData(this.storage, 'locations.selected', () => structuredClone(EMPTY_LOCATION)) diff --git a/desktop/src/shared/types/focuser.types.ts b/desktop/src/shared/types/focuser.types.ts index eb1e2f8ca..50bf8c5ea 100644 --- a/desktop/src/shared/types/focuser.types.ts +++ b/desktop/src/shared/types/focuser.types.ts @@ -33,10 +33,6 @@ export const EMPTY_FOCUSER: Focuser = { temperature: 0 } -export function focuserPreferenceKey(focuser: Focuser) { - return `focuser.${focuser.name}` -} - export interface FocuserPreference { stepsRelative?: number stepsAbsolute?: number From c9f513874e23386bcd3a22973d5861ab42c024d7 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 00:44:05 -0300 Subject: [PATCH 09/19] [api][desktop]: Support Rotator --- ...viceOrEntityParamMethodArgumentResolver.kt | 2 + .../api/rotators/RotatorController.kt | 15 +- .../api/rotators/RotatorEventHandler.kt | 59 ++++++++ .../api/rotators/RotatorMessageEvent.kt | 9 ++ .../api/rotators/RotatorSerializer.kt | 3 +- .../nebulosa/api/rotators/RotatorService.kt | 4 + desktop/app/main.ts | 5 +- desktop/src/app/app-routing.module.ts | 5 + desktop/src/app/app.module.ts | 2 + desktop/src/app/camera/camera.component.html | 12 +- desktop/src/app/home/home.component.html | 3 +- desktop/src/app/home/home.component.ts | 22 ++- .../src/app/rotator/rotator.component.html | 50 +++++++ .../src/app/rotator/rotator.component.scss | 0 desktop/src/app/rotator/rotator.component.ts | 134 ++++++++++++++++++ desktop/src/shared/services/api.service.ts | 39 +++++ .../shared/services/browser-window.service.ts | 8 +- .../src/shared/services/electron.service.ts | 4 + .../src/shared/services/preference.service.ts | 5 + desktop/src/shared/types/api.types.ts | 2 + desktop/src/shared/types/rotator.types.ts | 35 +++++ .../client/device/rotators/INDIRotator.kt | 80 ++++++++--- .../nebulosa/indi/device/rotator/Rotator.kt | 4 + .../rotator/RotatorMinMaxAngleChanged.kt | 5 + .../device/rotator/RotatorMovingChanged.kt | 5 + .../device/rotator/RotatorReversedChanged.kt | 5 + 26 files changed, 479 insertions(+), 38 deletions(-) create mode 100644 api/src/main/kotlin/nebulosa/api/rotators/RotatorEventHandler.kt create mode 100644 api/src/main/kotlin/nebulosa/api/rotators/RotatorMessageEvent.kt create mode 100644 desktop/src/app/rotator/rotator.component.html create mode 100644 desktop/src/app/rotator/rotator.component.scss create mode 100644 desktop/src/app/rotator/rotator.component.ts create mode 100644 desktop/src/shared/types/rotator.types.ts create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMinMaxAngleChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMovingChanged.kt create mode 100644 nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorReversedChanged.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 b3d139fb8..230794519 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 @@ -13,6 +13,7 @@ import nebulosa.indi.device.filterwheel.FilterWheel import nebulosa.indi.device.focuser.Focuser import nebulosa.indi.device.guide.GuideOutput import nebulosa.indi.device.mount.Mount +import nebulosa.indi.device.rotator.Rotator import org.springframework.core.MethodParameter import org.springframework.stereotype.Component import org.springframework.web.bind.support.WebDataBinderFactory @@ -35,6 +36,7 @@ class DeviceOrEntityParamMethodArgumentResolver( Mount::class.java to { connectionService.mount(it) }, Focuser::class.java to { connectionService.focuser(it) }, FilterWheel::class.java to { connectionService.wheel(it) }, + Rotator::class.java to { connectionService.rotator(it) }, GuideOutput::class.java to { connectionService.guideOutput(it) }, ) diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt index 719d4f861..264353875 100644 --- a/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorController.kt @@ -33,8 +33,16 @@ class RotatorController( rotatorService.disconnect(rotator) } + @PutMapping("{rotator}/reverse") + fun reverse( + rotator: Rotator, + @RequestParam enabled: Boolean, + ) { + rotatorService.reverse(rotator, enabled) + } + @PutMapping("{rotator}/move") - fun moveIn( + fun move( rotator: Rotator, @RequestParam @Valid @Range(min = 0, max = 360) angle: Double, ) { @@ -46,6 +54,11 @@ class RotatorController( rotatorService.abort(rotator) } + @PutMapping("{rotator}/home") + fun home(rotator: Rotator) { + rotatorService.home(rotator) + } + @PutMapping("{rotator}/sync") fun sync( rotator: Rotator, diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorEventHandler.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorEventHandler.kt new file mode 100644 index 000000000..b4fd46e07 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorEventHandler.kt @@ -0,0 +1,59 @@ +package nebulosa.api.rotators + +import io.reactivex.rxjava3.subjects.PublishSubject +import nebulosa.api.beans.annotations.Subscriber +import nebulosa.api.messages.MessageService +import nebulosa.indi.device.PropertyChangedEvent +import nebulosa.indi.device.rotator.Rotator +import nebulosa.indi.device.rotator.RotatorAttached +import nebulosa.indi.device.rotator.RotatorDetached +import nebulosa.indi.device.rotator.RotatorEvent +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import org.springframework.stereotype.Component +import java.io.Closeable +import java.util.concurrent.TimeUnit + +@Component +@Subscriber +class RotatorEventHandler( + private val messageService: MessageService, +) : Closeable { + + private val throttler = PublishSubject.create() + + init { + throttler + .throttleLast(1000, TimeUnit.MILLISECONDS) + .subscribe { sendUpdate(it.device!!) } + } + + @Subscribe(threadMode = ThreadMode.ASYNC) + fun onRotatorEvent(event: RotatorEvent) { + when (event) { + is PropertyChangedEvent -> throttler.onNext(event) + is RotatorAttached -> sendMessage(ROTATOR_ATTACHED, event.device) + is RotatorDetached -> sendMessage(ROTATOR_DETACHED, event.device) + } + } + + @Suppress("NOTHING_TO_INLINE") + private inline fun sendMessage(eventName: String, device: Rotator) { + messageService.sendMessage(RotatorMessageEvent(eventName, device)) + } + + fun sendUpdate(device: Rotator) { + sendMessage(ROTATOR_UPDATED, device) + } + + override fun close() { + throttler.onComplete() + } + + companion object { + + const val ROTATOR_UPDATED = "ROTATOR.UPDATED" + const val ROTATOR_ATTACHED = "ROTATOR.ATTACHED" + const val ROTATOR_DETACHED = "ROTATOR.DETACHED" + } +} diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorMessageEvent.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorMessageEvent.kt new file mode 100644 index 000000000..cdb79f400 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorMessageEvent.kt @@ -0,0 +1,9 @@ +package nebulosa.api.rotators + +import nebulosa.api.messages.DeviceMessageEvent +import nebulosa.indi.device.rotator.Rotator + +data class RotatorMessageEvent( + override val eventName: String, + override val device: Rotator, +) : DeviceMessageEvent diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt index f478f2f6e..851b48561 100644 --- a/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorSerializer.kt @@ -15,10 +15,11 @@ class RotatorSerializer : StdSerializer(Rotator::class.java) { gen.writeStringField("id", value.id) gen.writeStringField("name", value.name) gen.writeBooleanField("connected", value.connected) - // gen.writeBooleanField("moving", value.moving) + gen.writeBooleanField("moving", value.moving) gen.writeNumberField("angle", value.angle) gen.writeBooleanField("canAbort", value.canAbort) gen.writeBooleanField("canReverse", value.canReverse) + gen.writeBooleanField("reversed", value.reversed) gen.writeBooleanField("canHome", value.canHome) gen.writeBooleanField("canSync", value.canSync) gen.writeBooleanField("hasBacklashCompensation", value.hasBacklashCompensation) diff --git a/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt b/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt index 9a421378e..80d7537fb 100644 --- a/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt +++ b/api/src/main/kotlin/nebulosa/api/rotators/RotatorService.kt @@ -14,6 +14,10 @@ class RotatorService { rotator.disconnect() } + fun reverse(rotator: Rotator, enabled: Boolean) { + rotator.reverseRotator(enabled) + } + fun move(rotator: Rotator, angle: Double) { rotator.moveRotator(angle) } diff --git a/desktop/app/main.ts b/desktop/app/main.ts index 9130d7720..d671c4c9e 100644 --- a/desktop/app/main.ts +++ b/desktop/app/main.ts @@ -504,12 +504,11 @@ try { const height = Math.max(options?.minHeight ?? 0, Math.min(data, maxHeight)) // https://github.com/electron/electron/issues/16711#issuecomment-1311824063 - const resizable = window.isResizable() window.setResizable(true) window.setSize(width, height) - window.setResizable(resizable) + window.setResizable(serve) - console.info('window auto resized:', width, height) + console.info('window auto resized:', options.id, width, height) return true }) diff --git a/desktop/src/app/app-routing.module.ts b/desktop/src/app/app-routing.module.ts index 989fc38bb..bdd1b6a47 100644 --- a/desktop/src/app/app-routing.module.ts +++ b/desktop/src/app/app-routing.module.ts @@ -16,6 +16,7 @@ import { HomeComponent } from './home/home.component' import { ImageComponent } from './image/image.component' import { INDIComponent } from './indi/indi.component' import { MountComponent } from './mount/mount.component' +import { RotatorComponent } from './rotator/rotator.component' import { SequencerComponent } from './sequencer/sequencer.component' import { SettingsComponent } from './settings/settings.component' @@ -45,6 +46,10 @@ const routes: Routes = [ path: 'mount', component: MountComponent, }, + { + path: 'rotator', + component: RotatorComponent, + }, { path: 'guider', component: GuiderComponent, diff --git a/desktop/src/app/app.module.ts b/desktop/src/app/app.module.ts index 405e149d9..74dad403c 100644 --- a/desktop/src/app/app.module.ts +++ b/desktop/src/app/app.module.ts @@ -79,6 +79,7 @@ import { ImageComponent } from './image/image.component' import { INDIComponent } from './indi/indi.component' import { INDIPropertyComponent } from './indi/property/indi-property.component' import { MountComponent } from './mount/mount.component' +import { RotatorComponent } from './rotator/rotator.component' import { SequencerComponent } from './sequencer/sequencer.component' import { SettingsComponent } from './settings/settings.component' @@ -116,6 +117,7 @@ import { SettingsComponent } from './settings/settings.component' MoonComponent, MountComponent, NoDropdownDirective, + RotatorComponent, SequencerComponent, SettingsComponent, SkyObjectPipe, diff --git a/desktop/src/app/camera/camera.component.html b/desktop/src/app/camera/camera.component.html index 7470a5cde..0bac743f4 100644 --- a/desktop/src/app/camera/camera.component.html +++ b/desktop/src/app/camera/camera.component.html @@ -46,12 +46,12 @@
- Cooler ({{ camera.coolerPower | number: '1.1-1' }}%) + Cooler ({{ camera.coolerPower | number: '1.1-1' }}%)
- Dew heater + Dew heater
@@ -92,7 +92,7 @@
- Exposure Mode + Exposure Mode
@@ -149,7 +149,7 @@
- Subframe + Subframe
@@ -218,11 +218,11 @@
- Enabled + Enabled
- RA only + RA only
diff --git a/desktop/src/app/home/home.component.html b/desktop/src/app/home/home.component.html index 596744f34..9bd830683 100644 --- a/desktop/src/app/home/home.component.html +++ b/desktop/src/app/home/home.component.html @@ -75,7 +75,8 @@
- +
Rotator
diff --git a/desktop/src/app/home/home.component.ts b/desktop/src/app/home/home.component.ts index 982f10f41..fd5967889 100644 --- a/desktop/src/app/home/home.component.ts +++ b/desktop/src/app/home/home.component.ts @@ -13,6 +13,7 @@ import { Device } from '../../shared/types/device.types' import { Focuser } from '../../shared/types/focuser.types' import { CONNECTION_TYPES, ConnectionDetails, EMPTY_CONNECTION_DETAILS, HomeWindowType } from '../../shared/types/home.types' import { Mount } from '../../shared/types/mount.types' +import { Rotator } from '../../shared/types/rotator.types' import { FilterWheel } from '../../shared/types/wheel.types' import { deviceComparator } from '../../shared/utils/comparators' import { AppComponent } from '../app.component' @@ -22,6 +23,7 @@ type MappedDevice = { 'MOUNT': Mount 'FOCUSER': Focuser 'WHEEL': FilterWheel + 'ROTATOR': Rotator } @Component({ @@ -48,8 +50,8 @@ export class HomeComponent implements AfterContentInit, OnDestroy { mounts: Mount[] = [] focusers: Focuser[] = [] wheels: FilterWheel[] = [] + rotators: Rotator[] = [] domes: Camera[] = [] - rotators: Camera[] = [] switches: Camera[] = [] currentPage = 0 @@ -196,6 +198,16 @@ export class HomeComponent implements AfterContentInit, OnDestroy { }, ) + this.startListening('ROTATOR', + (device) => { + return this.rotators.push(device) + }, + (device) => { + this.rotators.splice(this.rotators.findIndex(e => e.id === device.id), 1) + return this.rotators.length + }, + ) + electron.on('CONNECTION.CLOSED', event => { if (this.connection?.id === event.id) { ngZone.run(() => { @@ -227,6 +239,7 @@ export class HomeComponent implements AfterContentInit, OnDestroy { this.mounts = await this.api.mounts() this.focusers = await this.api.focusers() this.wheels = await this.api.wheels() + this.rotators = await this.api.rotators() } } @@ -313,7 +326,8 @@ export class HomeComponent implements AfterContentInit, OnDestroy { : type === 'MOUNT' ? this.mounts : type === 'FOCUSER' ? this.focusers : type === 'WHEEL' ? this.wheels - : [] + : type === 'ROTATOR' ? this.rotators + : [] if (devices.length === 0) return if (devices.length === 1) return this.openDeviceWindow(type, devices[0] as any) @@ -346,6 +360,9 @@ export class HomeComponent implements AfterContentInit, OnDestroy { case 'WHEEL': this.browserWindow.openWheel({ bringToFront: true, data: device as FilterWheel }) break + case 'ROTATOR': + this.browserWindow.openRotator({ bringToFront: true, data: device as Rotator }) + break } } @@ -374,6 +391,7 @@ export class HomeComponent implements AfterContentInit, OnDestroy { case 'CAMERA': case 'FOCUSER': case 'WHEEL': + case 'ROTATOR': this.openDevice(type, type) break case 'GUIDER': diff --git a/desktop/src/app/rotator/rotator.component.html b/desktop/src/app/rotator/rotator.component.html new file mode 100644 index 000000000..845f17a58 --- /dev/null +++ b/desktop/src/app/rotator/rotator.component.html @@ -0,0 +1,50 @@ +
+
+
+ + + + + + +
+
+
+ + {{ moving ? 'moving' : 'idle' }} +
+
+
+
+
+ + + + +
+
+ + +
+ Reversed + +
+
+
+ + + + + + +
+
+
\ No newline at end of file diff --git a/desktop/src/app/rotator/rotator.component.scss b/desktop/src/app/rotator/rotator.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/desktop/src/app/rotator/rotator.component.ts b/desktop/src/app/rotator/rotator.component.ts new file mode 100644 index 000000000..dabb85bc6 --- /dev/null +++ b/desktop/src/app/rotator/rotator.component.ts @@ -0,0 +1,134 @@ +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 { PreferenceService } from '../../shared/services/preference.service' +import { EMPTY_ROTATOR, Rotator } from '../../shared/types/rotator.types' +import { AppComponent } from '../app.component' + +@Component({ + selector: 'app-rotator', + templateUrl: './rotator.component.html', + styleUrls: ['./rotator.component.scss'], +}) +export class RotatorComponent implements AfterViewInit, OnDestroy { + + readonly rotator = structuredClone(EMPTY_ROTATOR) + + moving = false + reversed = false + angle = 0 + + constructor( + private app: AppComponent, + private api: ApiService, + electron: ElectronService, + private preference: PreferenceService, + private route: ActivatedRoute, + ngZone: NgZone, + ) { + app.title = 'Rotator' + + electron.on('ROTATOR.UPDATED', event => { + if (event.device.id === this.rotator.id) { + ngZone.run(() => { + Object.assign(this.rotator, event.device) + this.update() + }) + } + }) + + electron.on('ROTATOR.DETACHED', event => { + if (event.device.id === this.rotator.id) { + ngZone.run(() => { + Object.assign(this.rotator, EMPTY_ROTATOR) + }) + } + }) + } + + async ngAfterViewInit() { + this.route.queryParams.subscribe(e => { + const rotator = JSON.parse(decodeURIComponent(e.data)) as Rotator + this.rotatorChanged(rotator) + }) + } + + @HostListener('window:unload') + ngOnDestroy() { + this.abort() + } + + async rotatorChanged(rotator?: Rotator) { + if (rotator && rotator.id) { + rotator = await this.api.rotator(rotator.id) + Object.assign(this.rotator, rotator) + + this.loadPreference() + this.update() + } + + if (this.app) { + this.app.subTitle = rotator?.name ?? '' + } + } + + connect() { + if (this.rotator.connected) { + this.api.rotatorDisconnect(this.rotator) + } else { + this.api.rotatorConnect(this.rotator) + } + } + + reverse(enabled: boolean) { + this.api.focuserReverse(this.rotator, enabled) + } + + async move() { + if (!this.moving) { + this.moving = true + await this.api.rotatorMove(this.rotator, this.angle) + this.savePreference() + } + } + + async sync() { + if (!this.moving) { + await this.api.rotatorSync(this.rotator, this.angle) + this.savePreference() + } + } + + abort() { + this.api.rotatorAbort(this.rotator) + } + + home() { + this.api.rotatorHome(this.rotator) + } + + private update() { + if (!this.rotator.id) { + return + } + + this.moving = this.rotator.moving + this.reversed = this.rotator.reversed + } + + private loadPreference() { + if (this.rotator.id) { + const preference = this.preference.rotatorPreference(this.rotator).get() + this.angle = preference.angle ?? 0 + } + } + + private savePreference() { + if (this.rotator.connected) { + const preference = this.preference.rotatorPreference(this.rotator).get() + preference.angle = this.angle + this.preference.rotatorPreference(this.rotator).set(preference) + } + } +} \ No newline at end of file diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts index 644ddc7d2..737c8bede 100644 --- a/desktop/src/shared/services/api.service.ts +++ b/desktop/src/shared/services/api.service.ts @@ -12,6 +12,7 @@ import { GuideDirection, GuideOutput, Guider, GuiderHistoryStep, SettleInfo } fr import { ConnectionStatus, ConnectionType, Equipment } from '../types/home.types' import { CoordinateInterpolation, DetectedStar, FOVCamera, FOVTelescope, ImageAnnotation, ImageInfo, ImageSaveDialog, ImageSolved, ImageTransformation } from '../types/image.types' import { CelestialLocationType, Mount, MountRemoteControl, MountRemoteControlType, SlewRate, TrackMode } from '../types/mount.types' +import { Rotator } from '../types/rotator.types' import { SequencePlan } from '../types/sequencer.types' import { PlateSolverPreference } from '../types/settings.types' import { FilterWheel } from '../types/wheel.types' @@ -249,6 +250,44 @@ export class ApiService { return this.http.put(`wheels/${wheel.id}/sync?names=${names.join(',')}`) } + // ROTATOR + + rotators() { + return this.http.get(`rotators`) + } + + rotator(id: string) { + return this.http.get(`rotators/${id}`) + } + + rotatorConnect(rotator: Rotator) { + return this.http.put(`rotators/${rotator.id}/connect`) + } + + rotatorDisconnect(rotator: Rotator) { + return this.http.put(`rotators/${rotator.id}/disconnect`) + } + + focuserReverse(rotator: Rotator, enabled: boolean) { + return this.http.put(`rotators/${rotator.id}/reverse?enabled=${enabled}`) + } + + rotatorMove(rotator: Rotator, angle: number) { + return this.http.put(`rotators/${rotator.id}/move?angle=${angle}`) + } + + rotatorAbort(rotator: Rotator) { + return this.http.put(`rotators/${rotator.id}/abort`) + } + + rotatorHome(rotator: Rotator) { + return this.http.put(`rotators/${rotator.id}/home`) + } + + rotatorSync(rotator: Rotator, angle: number) { + return this.http.put(`rotators/${rotator.id}/sync?angle=${angle}`) + } + // GUIDE OUTPUT guideOutputs() { diff --git a/desktop/src/shared/services/browser-window.service.ts b/desktop/src/shared/services/browser-window.service.ts index 62829131e..f1f890a56 100644 --- a/desktop/src/shared/services/browser-window.service.ts +++ b/desktop/src/shared/services/browser-window.service.ts @@ -8,6 +8,7 @@ import { Device } from '../types/device.types' import { Focuser } from '../types/focuser.types' import { ImageData, ImageSource } from '../types/image.types' import { Mount } from '../types/mount.types' +import { Rotator } from '../types/rotator.types' import { FilterWheel, WheelDialogInput } from '../types/wheel.types' import { ElectronService } from './electron.service' @@ -45,10 +46,15 @@ export class BrowserWindowService { } openWheel(options: OpenWindowOptionsWithData) { - Object.assign(options, { icon: 'filter-wheel', width: 285, height: 195 }) + Object.assign(options, { icon: 'filter-wheel', width: 280, height: 195 }) this.openWindow({ ...options, id: `wheel.${options.data.name}`, path: 'wheel' }) } + openRotator(options: OpenWindowOptionsWithData) { + Object.assign(options, { icon: 'rotate', width: 280, height: 210 }) + this.openWindow({ ...options, id: `rotator.${options.data.name}`, path: 'rotator' }) + } + openWheelDialog(options: OpenWindowOptionsWithData) { Object.assign(options, { icon: 'filter-wheel', width: 300, height: 217 }) return this.openModal({ ...options, id: `wheel.${options.data.wheel.name}.modal`, path: 'wheel' }) diff --git a/desktop/src/shared/services/electron.service.ts b/desktop/src/shared/services/electron.service.ts index 81278efc8..58a2c58d2 100644 --- a/desktop/src/shared/services/electron.service.ts +++ b/desktop/src/shared/services/electron.service.ts @@ -18,6 +18,7 @@ import { Focuser } from '../types/focuser.types' import { GuideOutput, Guider, GuiderHistoryStep, GuiderMessageEvent } from '../types/guider.types' import { ConnectionClosed } from '../types/home.types' import { Mount } from '../types/mount.types' +import { Rotator } from '../types/rotator.types' import { SequencerEvent } from '../types/sequencer.types' import { FilterWheel } from '../types/wheel.types' import { ApiService } from './api.service' @@ -36,6 +37,9 @@ type EventMappedType = { 'FOCUSER.UPDATED': DeviceMessageEvent 'FOCUSER.ATTACHED': DeviceMessageEvent 'FOCUSER.DETACHED': DeviceMessageEvent + 'ROTATOR.UPDATED': DeviceMessageEvent + 'ROTATOR.ATTACHED': DeviceMessageEvent + 'ROTATOR.DETACHED': DeviceMessageEvent 'WHEEL.UPDATED': DeviceMessageEvent 'WHEEL.ATTACHED': DeviceMessageEvent 'WHEEL.DETACHED': DeviceMessageEvent diff --git a/desktop/src/shared/services/preference.service.ts b/desktop/src/shared/services/preference.service.ts index 8670ff6a0..98b902672 100644 --- a/desktop/src/shared/services/preference.service.ts +++ b/desktop/src/shared/services/preference.service.ts @@ -8,6 +8,7 @@ import { Device } from '../types/device.types' import { Focuser, FocuserPreference } from '../types/focuser.types' import { ConnectionDetails, Equipment, HomePreference } from '../types/home.types' import { EMPTY_IMAGE_PREFERENCE, FOV, ImagePreference } from '../types/image.types' +import { Rotator, RotatorPreference } from '../types/rotator.types' import { EMPTY_PLATE_SOLVER_PREFERENCE, PlateSolverPreference, PlateSolverType } from '../types/settings.types' import { FilterWheel, WheelPreference } from '../types/wheel.types' import { LocalStorageService } from './local-storage.service' @@ -78,6 +79,10 @@ export class PreferenceService { return new PreferenceData(this.storage, `focuser.${focuser.name}`, {}) } + rotatorPreference(rotator: Rotator) { + return new PreferenceData(this.storage, `rotator.${rotator.name}`, {}) + } + readonly connections = new PreferenceData(this.storage, 'home.connections', () => []) readonly locations = new PreferenceData(this.storage, 'locations', () => [structuredClone(EMPTY_LOCATION)]) readonly selectedLocation = new PreferenceData(this.storage, 'locations.selected', () => structuredClone(EMPTY_LOCATION)) diff --git a/desktop/src/shared/types/api.types.ts b/desktop/src/shared/types/api.types.ts index f64c81ec0..65733134a 100644 --- a/desktop/src/shared/types/api.types.ts +++ b/desktop/src/shared/types/api.types.ts @@ -21,6 +21,8 @@ export const API_EVENT_TYPES = [ 'FOCUSER.UPDATED', 'FOCUSER.ATTACHED', 'FOCUSER.DETACHED', // Filter Wheel. 'WHEEL.UPDATED', 'WHEEL.ATTACHED', 'WHEEL.DETACHED', + // Rotator. + 'ROTATOR.UPDATED', 'ROTATOR.ATTACHED', 'ROTATOR.DETACHED', // Guide Output. 'GUIDE_OUTPUT.ATTACHED', 'GUIDE_OUTPUT.DETACHED', 'GUIDE_OUTPUT.UPDATED', // Guider. diff --git a/desktop/src/shared/types/rotator.types.ts b/desktop/src/shared/types/rotator.types.ts new file mode 100644 index 000000000..4348ce342 --- /dev/null +++ b/desktop/src/shared/types/rotator.types.ts @@ -0,0 +1,35 @@ +import { Device } from './device.types' + +export interface Rotator extends Device { + moving: boolean + angle: number + canAbort: boolean + canReverse: boolean + reversed: boolean + canSync: boolean + canHome: boolean + hasBacklashCompensation: boolean + minAngle: number + maxAngle: number +} + +export const EMPTY_ROTATOR: Rotator = { + sender: '', + id: '', + name: '', + moving: false, + angle: 0, + canAbort: false, + canReverse: false, + reversed: false, + canSync: false, + canHome: false, + hasBacklashCompensation: false, + minAngle: 0, + maxAngle: 0, + connected: false +} + +export interface RotatorPreference { + angle?: number +} diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt index dda3b206d..58a8566d4 100644 --- a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt @@ -2,11 +2,9 @@ package nebulosa.indi.client.device.rotators import nebulosa.indi.client.INDIClient import nebulosa.indi.client.device.INDIDevice +import nebulosa.indi.device.firstOnSwitch import nebulosa.indi.device.rotator.* -import nebulosa.indi.protocol.DefNumberVector -import nebulosa.indi.protocol.DefSwitchVector -import nebulosa.indi.protocol.INDIProtocol -import nebulosa.indi.protocol.SetNumberVector +import nebulosa.indi.protocol.* // https://github.com/indilib/indi/blob/master/libs/indibase/indirotatorinterface.cpp @@ -15,10 +13,12 @@ internal open class INDIRotator( override val name: String, ) : INDIDevice(), Rotator { + @Volatile final override var moving = false @Volatile final override var canAbort = false @Volatile final override var canHome = false @Volatile final override var canSync = false @Volatile final override var canReverse = false + @Volatile final override var reversed = false @Volatile final override var hasBacklashCompensation = false @Volatile final override var backslash = 0 @Volatile final override var angle = 0.0 @@ -29,39 +29,73 @@ internal open class INDIRotator( when (message) { is DefNumberVector -> { when (message.name) { - "ROTATOR_LIMITS_VALUE" -> { + "ABS_ROTATOR_ANGLE" -> { + val angle = message["ANGLE"] ?: return + minAngle = angle.min + maxAngle = angle.max + sender.fireOnEventReceived(RotatorMinMaxAngleChanged(this)) + + if (angle.value != 0.0) { + this.angle = angle.value + sender.fireOnEventReceived(RotatorAngleChanged(this)) + } + } + "SYNC_ROTATOR_ANGLE" -> { + canSync = true + sender.fireOnEventReceived(RotatorCanSyncChanged(this)) } } } is DefSwitchVector -> { when (message.name) { - "ABORT" -> { - if ("ABS_ROTATOR_ANGLE" in message) { - canAbort = true - sender.fireOnEventReceived(RotatorCanAbortChanged(this)) - } - - if ("SYNC_ROTATOR_ANGLE" in message) { - canSync = true - sender.fireOnEventReceived(RotatorCanSyncChanged(this)) - } + "ROTATOR_ABORT_MOTION" -> { + canAbort = true + sender.fireOnEventReceived(RotatorCanAbortChanged(this)) } - "HOME" -> { + "ROTATOR_HOME" -> { canHome = true sender.fireOnEventReceived(RotatorCanHomeChanged(this)) } "ROTATOR_REVERSE" -> { canReverse = true sender.fireOnEventReceived(RotatorCanReverseChanged(this)) + + val reversed = message.firstOnSwitch().name == "INDI_ENABLED" + + if (reversed != this.reversed) { + this.reversed = reversed + sender.fireOnEventReceived(RotatorReversedChanged(this)) + } } } } is SetNumberVector -> { when (message.name) { - "ANGLE" -> { - angle = (message["ABS_ROTATOR_ANGLE"] ?: return).value - sender.fireOnEventReceived(RotatorAngleChanged(this)) + "ABS_ROTATOR_ANGLE" -> { + val angle = message["ANGLE"] ?: return + + if (moving != message.isBusy) { + this.moving = message.isBusy + sender.fireOnEventReceived(RotatorMovingChanged(this)) + } + + if (angle.value != this.angle) { + this.angle = angle.value + sender.fireOnEventReceived(RotatorAngleChanged(this)) + } + } + } + } + is SetSwitchVector -> { + when (message.name) { + "ROTATOR_REVERSE" -> { + val reversed = message.firstOnSwitch().name == "INDI_ENABLED" + + if (reversed != this.reversed) { + this.reversed = reversed + sender.fireOnEventReceived(RotatorReversedChanged(this)) + } } } } @@ -72,18 +106,18 @@ internal open class INDIRotator( } override fun moveRotator(angle: Double) { - sendNewNumber("ANGLE", "ABS_ROTATOR_ANGLE" to angle) + sendNewNumber("ABS_ROTATOR_ANGLE", "ANGLE" to angle) } override fun syncRotator(angle: Double) { if (canSync) { - sendNewNumber("ANGLE", "SYNC_ROTATOR_ANGLE" to angle) + sendNewNumber("SYNC_ROTATOR_ANGLE", "ANGLE" to angle) } } override fun homeRotator() { if (canHome) { - sendNewSwitch("HOME", "ROTATOR_HOME" to true) + sendNewSwitch("ROTATOR_HOME", "HOME" to true) } } @@ -95,7 +129,7 @@ internal open class INDIRotator( override fun abortRotator() { if (canAbort) { - sendNewSwitch("ABORT", "ROTATOR_ABORT_MOTION" to true) + sendNewSwitch("ROTATOR_ABORT_MOTION", "ABORT" to true) } } diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt index 47bdb6fb4..d22861982 100644 --- a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/Rotator.kt @@ -4,6 +4,8 @@ import nebulosa.indi.device.Device interface Rotator : Device { + val moving: Boolean + val canAbort: Boolean val canHome: Boolean @@ -12,6 +14,8 @@ interface Rotator : Device { val canReverse: Boolean + val reversed: Boolean + val hasBacklashCompensation: Boolean val backslash: Int diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMinMaxAngleChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMinMaxAngleChanged.kt new file mode 100644 index 000000000..480b22a34 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMinMaxAngleChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorMinMaxAngleChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMovingChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMovingChanged.kt new file mode 100644 index 000000000..b6b41f0cb --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorMovingChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorMovingChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent diff --git a/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorReversedChanged.kt b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorReversedChanged.kt new file mode 100644 index 000000000..3566dbd37 --- /dev/null +++ b/nebulosa-indi-device/src/main/kotlin/nebulosa/indi/device/rotator/RotatorReversedChanged.kt @@ -0,0 +1,5 @@ +package nebulosa.indi.device.rotator + +import nebulosa.indi.device.PropertyChangedEvent + +data class RotatorReversedChanged(override val device: Rotator) : RotatorEvent, PropertyChangedEvent From 76e743113fefff21d0cb15c07574f68b29012135 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 11:05:08 -0300 Subject: [PATCH 10/19] [api][desktop]: Support Rotator --- .../src/app/focuser/focuser.component.html | 22 ++++++------ desktop/src/app/focuser/focuser.component.ts | 35 +++---------------- desktop/src/app/rotator/rotator.component.ts | 6 ++-- .../client/device/focusers/INDIFocuser.kt | 2 -- .../client/device/rotators/INDIRotator.kt | 23 ++++++------ 5 files changed, 29 insertions(+), 59 deletions(-) diff --git a/desktop/src/app/focuser/focuser.component.html b/desktop/src/app/focuser/focuser.component.html index 869d9bcb1..52fd7de6b 100644 --- a/desktop/src/app/focuser/focuser.component.html +++ b/desktop/src/app/focuser/focuser.component.html @@ -17,41 +17,41 @@
- {{ temperature | number: '1.2-2' }}°C + {{ focuser.temperature | number: '1.2-2' }}°C
- - + +
- - +
- - -
- -
diff --git a/desktop/src/app/focuser/focuser.component.ts b/desktop/src/app/focuser/focuser.component.ts index c474977d1..d25235a30 100644 --- a/desktop/src/app/focuser/focuser.component.ts +++ b/desktop/src/app/focuser/focuser.component.ts @@ -17,18 +17,6 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { readonly focuser = structuredClone(EMPTY_FOCUSER) moving = false - position = 0 - hasThermometer = false - temperature = 0 - canAbsoluteMove = false - canRelativeMove = false - canAbort = false - canReverse = false - reversed = false - canSync = false - hasBacklash = false - maxPosition = 0 - stepsRelative = 0 stepsAbsolute = 0 @@ -69,10 +57,10 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { hotkeys('shift+right', (event) => { event.preventDefault(); this.moveOut(0.5) }) hotkeys('space', (event) => { event.preventDefault(); this.abort() }) hotkeys('ctrl+enter', (event) => { event.preventDefault(); this.moveTo() }) - hotkeys('up', (event) => { event.preventDefault(); this.stepsRelative = Math.min(this.maxPosition, this.stepsRelative + 1) }) + hotkeys('up', (event) => { event.preventDefault(); this.stepsRelative = Math.min(this.focuser.maxPosition, this.stepsRelative + 1) }) hotkeys('down', (event) => { event.preventDefault(); this.stepsRelative = Math.max(0, this.stepsRelative - 1) }) hotkeys('-', (event) => { event.preventDefault(); this.stepsAbsolute = Math.max(0, this.stepsAbsolute - 1) }) - hotkeys('=', (event) => { event.preventDefault(); this.stepsAbsolute = Math.min(this.maxPosition, this.stepsAbsolute + 1) }) + hotkeys('=', (event) => { event.preventDefault(); this.stepsAbsolute = Math.min(this.focuser.maxPosition, this.stepsAbsolute + 1) }) } async ngAfterViewInit() { @@ -126,7 +114,7 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { } async moveTo() { - if (!this.moving && this.stepsAbsolute !== this.position) { + if (!this.moving && this.stepsAbsolute !== this.focuser.position) { this.moving = true await this.api.focuserMoveTo(this.focuser, this.stepsAbsolute) this.savePreference() @@ -145,22 +133,9 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { } private update() { - if (!this.focuser.id) { - return + if (this.focuser.id) { + this.moving = this.focuser.moving } - - this.moving = this.focuser.moving - this.position = this.focuser.position - this.hasThermometer = this.focuser.hasThermometer - this.temperature = this.focuser.temperature - this.canAbsoluteMove = this.focuser.canAbsoluteMove - this.canRelativeMove = this.focuser.canRelativeMove - this.canAbort = this.focuser.canAbort - this.canReverse = this.focuser.canReverse - this.reversed = this.focuser.reversed - this.canSync = this.focuser.canSync - this.hasBacklash = this.focuser.hasBacklash - this.maxPosition = this.focuser.maxPosition } private loadPreference() { diff --git a/desktop/src/app/rotator/rotator.component.ts b/desktop/src/app/rotator/rotator.component.ts index dabb85bc6..d0bd81edd 100644 --- a/desktop/src/app/rotator/rotator.component.ts +++ b/desktop/src/app/rotator/rotator.component.ts @@ -110,11 +110,9 @@ export class RotatorComponent implements AfterViewInit, OnDestroy { private update() { if (!this.rotator.id) { - return + this.moving = this.rotator.moving + this.reversed = this.rotator.reversed } - - this.moving = this.rotator.moving - this.reversed = this.rotator.reversed } private loadPreference() { diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/focusers/INDIFocuser.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/focusers/INDIFocuser.kt index cde8ff15e..7a42caa30 100644 --- a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/focusers/INDIFocuser.kt +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/focusers/INDIFocuser.kt @@ -34,14 +34,12 @@ internal open class INDIFocuser( "FOCUS_ABORT_MOTION" -> { if (message is DefSwitchVector) { canAbort = true - sender.fireOnEventReceived(FocuserCanAbortChanged(this)) } } "FOCUS_REVERSE_MOTION" -> { if (message is DefSwitchVector) { canReverse = true - sender.fireOnEventReceived(FocuserCanReverseChanged(this)) } diff --git a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt index 58a8566d4..c56f75645 100644 --- a/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt +++ b/nebulosa-indi-client/src/main/kotlin/nebulosa/indi/client/device/rotators/INDIRotator.kt @@ -61,12 +61,7 @@ internal open class INDIRotator( canReverse = true sender.fireOnEventReceived(RotatorCanReverseChanged(this)) - val reversed = message.firstOnSwitch().name == "INDI_ENABLED" - - if (reversed != this.reversed) { - this.reversed = reversed - sender.fireOnEventReceived(RotatorReversedChanged(this)) - } + handleReversed(message) } } } @@ -90,12 +85,7 @@ internal open class INDIRotator( is SetSwitchVector -> { when (message.name) { "ROTATOR_REVERSE" -> { - val reversed = message.firstOnSwitch().name == "INDI_ENABLED" - - if (reversed != this.reversed) { - this.reversed = reversed - sender.fireOnEventReceived(RotatorReversedChanged(this)) - } + handleReversed(message) } } } @@ -105,6 +95,15 @@ internal open class INDIRotator( super.handleMessage(message) } + private fun handleReversed(message: SwitchVector<*>) { + val reversed = message.firstOnSwitch().name == "INDI_ENABLED" + + if (reversed != this.reversed) { + this.reversed = reversed + sender.fireOnEventReceived(RotatorReversedChanged(this)) + } + } + override fun moveRotator(angle: Double) { sendNewNumber("ABS_ROTATOR_ANGLE", "ANGLE" to angle) } From 92657f0317398aeb4a139941ee6ec9dc19d752dc Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 11:15:21 -0300 Subject: [PATCH 11/19] [desktop]: Add Rotator screenshot --- desktop/README.md | 4 ++++ desktop/rotator.png | Bin 0 -> 10476 bytes 2 files changed, 4 insertions(+) create mode 100644 desktop/rotator.png diff --git a/desktop/README.md b/desktop/README.md index e48a26619..b6880a99a 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -22,6 +22,10 @@ The complete integrated solution for all of your astronomical imaging needs. ![](filter-wheel.png) +## Rotator + +![](rotator.png) + ## Guider ![](guider.png) diff --git a/desktop/rotator.png b/desktop/rotator.png new file mode 100644 index 0000000000000000000000000000000000000000..14573a6605b77574c3c9a87a9624c3193275788c GIT binary patch literal 10476 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 Date: Mon, 20 May 2024 11:38:24 -0300 Subject: [PATCH 12/19] [api]: Unregister rotators when connection is closed --- .../nebulosa/indi/device/AbstractINDIDeviceProvider.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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 22b8c030b..53e55e8b8 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 @@ -218,6 +218,11 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { unregisterFocuser(it) } + rotators().forEach { + it.close() + unregisterRotator(it) + } + gps().forEach { it.close() unregisterGPS(it) @@ -227,6 +232,7 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { mounts.clear() wheels.clear() focusers.clear() + rotators.clear() gps.clear() guideOutputs.clear() thermometers.clear() From e8d71262f69dc063cc0d125e6d222937efea9a5c Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 11:40:36 -0300 Subject: [PATCH 13/19] [desktop]: Clear all devices when connection is closed --- desktop/src/app/home/home.component.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/desktop/src/app/home/home.component.ts b/desktop/src/app/home/home.component.ts index fd5967889..37a9d7173 100644 --- a/desktop/src/app/home/home.component.ts +++ b/desktop/src/app/home/home.component.ts @@ -444,14 +444,6 @@ export class HomeComponent implements AfterContentInit, OnDestroy { } } catch { this.connection.connected = false - - this.cameras = [] - this.mounts = [] - this.focusers = [] - this.wheels = [] - this.domes = [] - this.rotators = [] - this.switches = [] } } else { const statuses = await this.api.connectionStatuses() @@ -474,6 +466,16 @@ export class HomeComponent implements AfterContentInit, OnDestroy { } } } + + if (!this.connection?.connected) { + this.cameras = [] + this.mounts = [] + this.focusers = [] + this.wheels = [] + this.domes = [] + this.rotators = [] + this.switches = [] + } } private scrollPageOf(element: Element) { From 6cb94d63f836e2889c794be7f6e4f54175a52ed6 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 11:42:27 -0300 Subject: [PATCH 14/19] [api]: Clear listeners on close --- .../kotlin/nebulosa/indi/protocol/parser/INDIProtocolReader.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/nebulosa-indi-protocol/src/main/kotlin/nebulosa/indi/protocol/parser/INDIProtocolReader.kt b/nebulosa-indi-protocol/src/main/kotlin/nebulosa/indi/protocol/parser/INDIProtocolReader.kt index 2c0c92fb4..40167f9d1 100644 --- a/nebulosa-indi-protocol/src/main/kotlin/nebulosa/indi/protocol/parser/INDIProtocolReader.kt +++ b/nebulosa-indi-protocol/src/main/kotlin/nebulosa/indi/protocol/parser/INDIProtocolReader.kt @@ -60,6 +60,7 @@ class INDIProtocolReader( if (!running) return running = false + listeners.clear() interrupt() } From 0a9b8c1d4e76087411a55b5bddb30e04f4784834 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 12:11:01 -0300 Subject: [PATCH 15/19] [api]: Implement ASCOM Rotator API --- .../alpaca/api/AlpacaRotatorService.kt | 48 +++++++++++++++++++ .../nebulosa/alpaca/api/AlpacaService.kt | 2 + 2 files changed, 50 insertions(+) create mode 100644 nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaRotatorService.kt diff --git a/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaRotatorService.kt b/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaRotatorService.kt new file mode 100644 index 000000000..da6167647 --- /dev/null +++ b/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaRotatorService.kt @@ -0,0 +1,48 @@ +package nebulosa.alpaca.api + +import retrofit2.Call +import retrofit2.http.* + +interface AlpacaRotatorService : AlpacaDeviceService { + + @GET("api/v1/rotator/{id}/connected") + override fun isConnected(@Path("id") id: Int): Call + + @FormUrlEncoded + @PUT("api/v1/rotator/{id}/connected") + override fun connect(@Path("id") id: Int, @Field("Connected") connected: Boolean): Call + + @GET("api/v1/rotator/{id}/canreverse") + fun canReverse(@Path("id") id: Int): Call + + @GET("api/v1/rotator/{id}/ismoving") + fun isMoving(@Path("id") id: Int): Call + + @GET("api/v1/rotator/{id}/reverse") + fun isReversed(@Path("id") id: Int): Call + + @GET("api/v1/rotator/{id}/position") + fun position(@Path("id") id: Int): Call + + @GET("api/v1/rotator/{id}/stepsize") + fun stepSize(@Path("id") id: Int): Call + + @FormUrlEncoded + @PUT("api/v1/rotator/{id}/reverse") + fun reverse(@Path("id") id: Int, @Field("Reverse") reverse: Boolean): Call + + @PUT("api/v1/rotator/{id}/halt") + fun halt(@Path("id") id: Int): Call + + @FormUrlEncoded + @PUT("api/v1/rotator/{id}/move") + fun move(@Path("id") id: Int, @Field("Position") position: Double): Call + + @FormUrlEncoded + @PUT("api/v1/rotator/{id}/moveabsolute") + fun moveTo(@Path("id") id: Int, @Field("Position") position: Double): Call + + @FormUrlEncoded + @PUT("api/v1/rotator/{id}/sync") + fun sync(@Path("id") id: Int, @Field("Position") position: Double): Call +} diff --git a/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaService.kt b/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaService.kt index 94c180c0a..16e9b6bd0 100644 --- a/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaService.kt +++ b/nebulosa-alpaca-api/src/main/kotlin/nebulosa/alpaca/api/AlpacaService.kt @@ -24,4 +24,6 @@ class AlpacaService( val filterWheel by lazy { retrofit.create() } val focuser by lazy { retrofit.create() } + + val rotator by lazy { retrofit.create() } } From 5425a2336be166889c2724613ee3bf53d5fbc5a5 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 12:22:29 -0300 Subject: [PATCH 16/19] [api]: Support ASCOM Rotator --- .../alpaca/indi/client/AlpacaClient.kt | 11 +- .../indi/device/rotators/ASCOMRotator.kt | 112 ++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/client/AlpacaClient.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/client/AlpacaClient.kt index 91a135312..89eaaf687 100644 --- a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/client/AlpacaClient.kt +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/client/AlpacaClient.kt @@ -5,6 +5,7 @@ import nebulosa.alpaca.api.DeviceType import nebulosa.alpaca.indi.device.cameras.ASCOMCamera import nebulosa.alpaca.indi.device.focusers.ASCOMFocuser import nebulosa.alpaca.indi.device.mounts.ASCOMMount +import nebulosa.alpaca.indi.device.rotators.ASCOMRotator import nebulosa.alpaca.indi.device.wheels.ASCOMFilterWheel import nebulosa.indi.device.AbstractINDIDeviceProvider import nebulosa.indi.protocol.INDIProtocol @@ -21,7 +22,7 @@ data class AlpacaClient( override val id = UUID.randomUUID().toString() - override fun sendMessageToServer(message: INDIProtocol) {} + override fun sendMessageToServer(message: INDIProtocol) = Unit fun discovery() { val response = service.management.configuredDevices().execute() @@ -59,7 +60,13 @@ data class AlpacaClient( } } } - DeviceType.ROTATOR -> Unit + DeviceType.ROTATOR -> { + with(ASCOMRotator(device, service.rotator, this)) { + if (registerRotator(this)) { + initialize() + } + } + } DeviceType.DOME -> Unit DeviceType.SWITCH -> Unit DeviceType.COVER_CALIBRATOR -> Unit diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt new file mode 100644 index 000000000..3516873bc --- /dev/null +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt @@ -0,0 +1,112 @@ +package nebulosa.alpaca.indi.device.rotators + +import nebulosa.alpaca.api.AlpacaRotatorService +import nebulosa.alpaca.api.ConfiguredDevice +import nebulosa.alpaca.indi.client.AlpacaClient +import nebulosa.alpaca.indi.device.ASCOMDevice +import nebulosa.indi.device.Device +import nebulosa.indi.device.rotator.* +import nebulosa.indi.protocol.INDIProtocol + +@Suppress("RedundantModalityModifier") +data class ASCOMRotator( + override val device: ConfiguredDevice, + override val service: AlpacaRotatorService, + override val sender: AlpacaClient, +) : ASCOMDevice(), Rotator { + + @Volatile final override var angle = 0.0 + @Volatile final override var minAngle = 0.0 + @Volatile final override var maxAngle = 360.0 + @Volatile final override var moving = false + @Volatile final override var canAbort = true + @Volatile final override var canHome = false + @Volatile final override var canSync = true + @Volatile final override var canReverse = false + @Volatile final override var reversed = false + @Volatile final override var hasBacklashCompensation = false + @Volatile final override var backslash = 0 + + override val snoopedDevices = emptyList() + + override fun onConnected() { + processCapabilities() + processPosition() + } + + override fun onDisconnected() {} + + override fun refresh(elapsedTimeInSeconds: Long) { + super.refresh(elapsedTimeInSeconds) + + if (connected) { + processPosition() + processMoving() + processReversed() + } + } + + override fun snoop(devices: Iterable) {} + + override fun moveRotator(angle: Double) { + service.moveTo(device.number, angle).doRequest() + } + + override fun syncRotator(angle: Double) { + if (canSync) { + service.sync(device.number, angle).doRequest() + } + } + + override fun homeRotator() = Unit + + override fun reverseRotator(enable: Boolean) { + if (canReverse) { + service.reverse(device.number, enable).doRequest() + } + } + + override fun abortRotator() { + if (canAbort) { + service.halt(device.number).doRequest() + } + } + + override fun handleMessage(message: INDIProtocol) = Unit + + private fun processCapabilities() { + service.canReverse(device.number).doRequest { + if (it.value != canReverse) { + canReverse = it.value + sender.fireOnEventReceived(RotatorCanReverseChanged(this)) + } + } + } + + private fun processPosition() { + service.position(device.number).doRequest { + if (it.value != angle) { + angle = it.value + sender.fireOnEventReceived(RotatorAngleChanged(this)) + } + } + } + + private fun processMoving() { + service.isMoving(device.number).doRequest { + if (it.value != moving) { + moving = it.value + sender.fireOnEventReceived(RotatorMovingChanged(this)) + } + } + } + + private fun processReversed() { + service.isReversed(device.number).doRequest { + if (it.value != reversed) { + reversed = it.value + sender.fireOnEventReceived(RotatorReversedChanged(this)) + } + } + } +} From d500bc065def61903fbd67730c1992e9ca4d4420 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 16:06:12 -0300 Subject: [PATCH 17/19] [api][desktop]: Support ASCOM Rotator --- desktop/src/app/rotator/rotator.component.ts | 2 +- .../alpaca/indi/device/ASCOMDevice.kt | 19 ++++++---- .../indi/device/AbstractINDIDeviceProvider.kt | 35 ++++--------------- 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/desktop/src/app/rotator/rotator.component.ts b/desktop/src/app/rotator/rotator.component.ts index d0bd81edd..20843f897 100644 --- a/desktop/src/app/rotator/rotator.component.ts +++ b/desktop/src/app/rotator/rotator.component.ts @@ -109,7 +109,7 @@ export class RotatorComponent implements AfterViewInit, OnDestroy { } private update() { - if (!this.rotator.id) { + if (this.rotator.id) { this.moving = this.rotator.moving this.reversed = this.rotator.reversed } diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt index 1eec61374..af58c5b9d 100644 --- a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt @@ -84,23 +84,28 @@ abstract class ASCOMDevice : Device, Resettable { protected fun > Call.doRequest(): T? { try { - val response = execute().body() + val request = request() + val response = execute() + val body = response.body() - return if (response == null) { - LOG.warn("response has no body. device={}, url={}", name, request().url) + return if (body == null) { + LOG.warn("response has no body. device={}, request={} {}, response={}", name, request.method, request.url, response.code()) null - } else if (response.errorNumber != 0) { - val message = response.errorMessage + } else if (body.errorNumber != 0) { + val message = body.errorMessage if (message.isNotEmpty()) { addMessageAndFireEvent("[%s]: %s".format(LocalDateTime.now(), message)) } - // LOG.warn("unsuccessful response. device={}, code={}, message={}", name, response.errorNumber, response.errorMessage) + LOG.warn( + "unsuccessful response. device={}, request={} {}, errorNumber={}, message={}", + name, request.method, request.url, body.errorNumber, body.errorMessage + ) null } else { - response + body } } catch (e: HttpException) { LOG.error("unexpected response. device=$name", e) 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 53e55e8b8..235b7e947 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 @@ -198,35 +198,12 @@ abstract class AbstractINDIDeviceProvider : INDIDeviceProvider { } override fun close() { - cameras().forEach { - it.close() - unregisterCamera(it) - } - - mounts().forEach { - it.close() - unregisterMount(it) - } - - wheels().forEach { - it.close() - unregisterFilterWheel(it) - } - - focusers().forEach { - it.close() - unregisterFocuser(it) - } - - rotators().forEach { - it.close() - unregisterRotator(it) - } - - gps().forEach { - it.close() - unregisterGPS(it) - } + cameras().onEach(Device::close).onEach(::unregisterCamera) + mounts().onEach(Device::close).onEach(::unregisterMount) + wheels().onEach(Device::close).onEach(::unregisterFilterWheel) + focusers().onEach(Device::close).onEach(::unregisterFocuser) + rotators().onEach(Device::close).onEach(::unregisterRotator) + gps().onEach(Device::close).onEach(::unregisterGPS) cameras.clear() mounts.clear() From 53507e60a40188f112742bfdfd9f62e88e7dd3af Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 16:27:24 -0300 Subject: [PATCH 18/19] [api][desktop]: Allow Camera snoop rotators --- .../nebulosa/api/cameras/CameraController.kt | 5 +- desktop/src/app/camera/camera.component.ts | 83 ++++++++++++------- desktop/src/shared/services/api.service.ts | 4 +- desktop/src/shared/types/home.types.ts | 2 + .../alpaca/indi/device/ASCOMDevice.kt | 12 +-- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/api/src/main/kotlin/nebulosa/api/cameras/CameraController.kt b/api/src/main/kotlin/nebulosa/api/cameras/CameraController.kt index 86c25dc79..7e64d19d7 100644 --- a/api/src/main/kotlin/nebulosa/api/cameras/CameraController.kt +++ b/api/src/main/kotlin/nebulosa/api/cameras/CameraController.kt @@ -6,6 +6,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.hibernate.validator.constraints.Range import org.springframework.web.bind.annotation.* @@ -39,8 +40,8 @@ class CameraController( @PutMapping("{camera}/snoop") fun snoop( camera: Camera, - mount: Mount?, wheel: FilterWheel?, focuser: Focuser?, - ) = cameraService.snoop(camera, mount, wheel, focuser) + mount: Mount?, wheel: FilterWheel?, focuser: Focuser?, rotator: Rotator? + ) = cameraService.snoop(camera, mount, wheel, focuser, rotator) @PutMapping("{camera}/cooler") fun cooler( diff --git a/desktop/src/app/camera/camera.component.ts b/desktop/src/app/camera/camera.component.ts index c4b775df8..0bf5944ec 100644 --- a/desktop/src/app/camera/camera.component.ts +++ b/desktop/src/app/camera/camera.component.ts @@ -10,9 +10,11 @@ import { BrowserWindowService } from '../../shared/services/browser-window.servi import { ElectronService } from '../../shared/services/electron.service' import { PreferenceService } from '../../shared/services/preference.service' import { Camera, CameraDialogInput, CameraDialogMode, CameraPreference, CameraStartCapture, EMPTY_CAMERA, EMPTY_CAMERA_START_CAPTURE, ExposureMode, ExposureTimeUnit, FrameType, updateCameraStartCaptureFromCamera } from '../../shared/types/camera.types' +import { Device } from '../../shared/types/device.types' import { Focuser } from '../../shared/types/focuser.types' import { Equipment } from '../../shared/types/home.types' import { Mount } from '../../shared/types/mount.types' +import { Rotator } from '../../shared/types/rotator.types' import { FilterWheel } from '../../shared/types/wheel.types' import { AppComponent } from '../app.component' @@ -103,6 +105,11 @@ export class CameraComponent implements AfterContentInit, OnDestroy { label: 'Focuser', menu: [], }, + { + icon: 'mdi mdi-rotate-right', + label: 'Rotator', + menu: [], + }, ] }, ] @@ -215,6 +222,14 @@ export class CameraComponent implements AfterContentInit, OnDestroy { } }) + electron.on('ROTATOR.UPDATED', event => { + if (event.device.id === this.equipment.rotator?.id) { + ngZone.run(() => { + Object.assign(this.equipment.rotator!, event.device) + }) + } + }) + electron.on('CALIBRATION.CHANGED', () => { ngZone.run(() => this.loadCalibrationGroups()) }) @@ -289,23 +304,21 @@ export class CameraComponent implements AfterContentInit, OnDestroy { } private async loadEquipment() { - const mounts = await this.api.mounts() - this.equipment.mount = mounts.find(e => e.name === this.equipment.mount?.name) - const buildStartTooltip = () => { this.startTooltip = `MOUNT: ${this.equipment.mount?.name ?? 'None'} FILTER WHEEL: ${this.equipment.wheel?.name ?? 'None'} - FOCUSER: ${this.equipment.focuser?.name ?? 'None'}` + FOCUSER: ${this.equipment.focuser?.name ?? 'None'} + ROTATOR: ${this.equipment.rotator?.name ?? 'None'}` } - const makeMountItem = (mount?: Mount) => { + const makeItem = (checked: boolean, command: () => void, device?: Device) => { return { - icon: mount ? 'mdi mdi-connection' : 'mdi mdi-close', - label: mount?.name ?? 'None', - checked: this.equipment.mount?.name === mount?.name, + icon: device ? 'mdi mdi-connection' : 'mdi mdi-close', + label: device?.name ?? 'None', + checked, command: async (event: SlideMenuItemCommandEvent) => { - this.equipment.mount = mount + command() buildStartTooltip() this.preference.equipmentForDevice(this.camera).set(this.equipment) event.parent?.menu?.forEach(item => item.checked = item === event.item) @@ -313,27 +326,28 @@ export class CameraComponent implements AfterContentInit, OnDestroy { } } + // MOUNT + + const mounts = await this.api.mounts() + this.equipment.mount = mounts.find(e => e.name === this.equipment.mount?.name) + + const makeMountItem = (mount?: Mount) => { + return makeItem(this.equipment.mount?.name === mount?.name, () => this.equipment.mount = mount, mount) + } + this.cameraModel[1].menu![0].menu!.push(makeMountItem()) for (const mount of mounts) { this.cameraModel[1].menu![0].menu!.push(makeMountItem(mount)) } + // FILTER WHEEL + const wheels = await this.api.wheels() this.equipment.wheel = wheels.find(e => e.name === this.equipment.wheel?.name) const makeWheelItem = (wheel?: FilterWheel) => { - return { - icon: wheel ? 'mdi mdi-connection' : 'mdi mdi-close', - label: wheel?.name ?? 'None', - checked: this.equipment.wheel?.name === wheel?.name, - command: async (event: SlideMenuItemCommandEvent) => { - this.equipment.wheel = wheel - buildStartTooltip() - this.preference.equipmentForDevice(this.camera).set(this.equipment) - event.parent?.menu?.forEach(item => item.checked = item === event.item) - }, - } + return makeItem(this.equipment.wheel?.name === wheel?.name, () => this.equipment.wheel = wheel, wheel) } this.cameraModel[1].menu![1].menu!.push(makeWheelItem()) @@ -342,21 +356,13 @@ export class CameraComponent implements AfterContentInit, OnDestroy { this.cameraModel[1].menu![1].menu!.push(makeWheelItem(wheel)) } + // FOCUSER + const focusers = await this.api.focusers() this.equipment.focuser = focusers.find(e => e.name === this.equipment.focuser?.name) const makeFocuserItem = (focuser?: Focuser) => { - return { - icon: focuser ? 'mdi mdi-connection' : 'mdi mdi-close', - label: focuser?.name ?? 'None', - checked: this.equipment.focuser?.name === focuser?.name, - command: async (event: SlideMenuItemCommandEvent) => { - this.equipment.focuser = focuser - buildStartTooltip() - this.preference.equipmentForDevice(this.camera).set(this.equipment) - event.parent?.menu?.forEach(item => item.checked = item === event.item) - }, - } + return makeItem(this.equipment.focuser?.name === focuser?.name, () => this.equipment.focuser = focuser, focuser) } this.cameraModel[1].menu![2].menu!.push(makeFocuserItem()) @@ -365,6 +371,21 @@ export class CameraComponent implements AfterContentInit, OnDestroy { this.cameraModel[1].menu![2].menu!.push(makeFocuserItem(focuser)) } + // ROTATOR + + const rotators = await this.api.rotators() + this.equipment.rotator = rotators.find(e => e.name === this.equipment.rotator?.name) + + const makeRotatorItem = (rotator?: Rotator) => { + return makeItem(this.equipment.rotator?.name === rotator?.name, () => this.equipment.rotator = rotator, rotator) + } + + this.cameraModel[1].menu![3].menu!.push(makeRotatorItem()) + + for (const rotator of rotators) { + this.cameraModel[1].menu![3].menu!.push(makeRotatorItem(rotator)) + } + buildStartTooltip() } diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts index 737c8bede..426feeabc 100644 --- a/desktop/src/shared/services/api.service.ts +++ b/desktop/src/shared/services/api.service.ts @@ -70,8 +70,8 @@ export class ApiService { // TODO: Rotator cameraSnoop(camera: Camera, equipment: Equipment) { - const { mount, wheel, focuser } = equipment - const query = this.http.query({ mount: mount?.name, wheel: wheel?.name, focuser: focuser?.name }) + const { mount, wheel, focuser, rotator } = equipment + const query = this.http.query({ mount: mount?.name, wheel: wheel?.name, focuser: focuser?.name, rotator: rotator?.name }) return this.http.put(`cameras/${camera.id}/snoop?${query}`) } diff --git a/desktop/src/shared/types/home.types.ts b/desktop/src/shared/types/home.types.ts index c8f4a28d3..fd22c33fc 100644 --- a/desktop/src/shared/types/home.types.ts +++ b/desktop/src/shared/types/home.types.ts @@ -1,6 +1,7 @@ import { Camera } 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 HomeWindowType = 'CAMERA' | 'MOUNT' | 'GUIDER' | 'WHEEL' | 'FOCUSER' | 'DOME' | 'ROTATOR' | 'SWITCH' | @@ -45,4 +46,5 @@ export interface Equipment { mount?: Mount focuser?: Focuser wheel?: FilterWheel + rotator?: Rotator } diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt index af58c5b9d..6a58bb809 100644 --- a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/ASCOMDevice.kt @@ -7,6 +7,7 @@ import nebulosa.alpaca.indi.client.AlpacaClient import nebulosa.common.Resettable import nebulosa.common.time.Stopwatch import nebulosa.indi.device.* +import nebulosa.log.debug import nebulosa.log.loggerFor import retrofit2.Call import retrofit2.HttpException @@ -89,7 +90,7 @@ abstract class ASCOMDevice : Device, Resettable { val body = response.body() return if (body == null) { - LOG.warn("response has no body. device={}, request={} {}, response={}", name, request.method, request.url, response.code()) + LOG.debug { "response has no body. device=%s, request=%s %s, response=%s".format(name, request.method, request.url, response) } null } else if (body.errorNumber != 0) { val message = body.errorMessage @@ -98,10 +99,11 @@ abstract class ASCOMDevice : Device, Resettable { addMessageAndFireEvent("[%s]: %s".format(LocalDateTime.now(), message)) } - LOG.warn( - "unsuccessful response. device={}, request={} {}, errorNumber={}, message={}", - name, request.method, request.url, body.errorNumber, body.errorMessage - ) + LOG.debug { + "unsuccessful response. device=%s, request=%s %s, errorNumber=%s, message=%s".format( + name, request.method, request.url, body.errorNumber, body.errorMessage + ) + } null } else { From 6447d4937617ab355b3157125bd3706aa235ee98 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Mon, 20 May 2024 16:37:31 -0300 Subject: [PATCH 19/19] [api]: Fix empty block of code --- .../kotlin/nebulosa/alpaca/indi/device/mounts/ASCOMMount.kt | 6 +++--- .../nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt | 4 ++-- .../nebulosa/alpaca/indi/device/wheels/ASCOMFilterWheel.kt | 6 +++--- .../nebulosa/guiding/internal/IdentityGuideAlgorithm.kt | 2 +- .../main/kotlin/nebulosa/guiding/internal/RandomDither.kt | 2 +- .../src/main/kotlin/nebulosa/skycatalog/SkyCatalog.kt | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/mounts/ASCOMMount.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/mounts/ASCOMMount.kt index 90c54264b..4f7dce29e 100644 --- a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/mounts/ASCOMMount.kt +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/mounts/ASCOMMount.kt @@ -224,9 +224,9 @@ data class ASCOMMount( service.utcDate(device.number, dateTime.toInstant()).doRequest() } - override fun snoop(devices: Iterable) {} + override fun snoop(devices: Iterable) = Unit - override fun handleMessage(message: INDIProtocol) {} + override fun handleMessage(message: INDIProtocol) = Unit override fun onConnected() { processCapabilities() @@ -237,7 +237,7 @@ data class ASCOMMount( equatorialSystem = service.equatorialSystem(device.number).doRequest()?.value ?: equatorialSystem } - override fun onDisconnected() {} + override fun onDisconnected() = Unit override fun reset() { super.reset() diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt index 3516873bc..26e372fe6 100644 --- a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/rotators/ASCOMRotator.kt @@ -34,7 +34,7 @@ data class ASCOMRotator( processPosition() } - override fun onDisconnected() {} + override fun onDisconnected() = Unit override fun refresh(elapsedTimeInSeconds: Long) { super.refresh(elapsedTimeInSeconds) @@ -46,7 +46,7 @@ data class ASCOMRotator( } } - override fun snoop(devices: Iterable) {} + override fun snoop(devices: Iterable) = Unit override fun moveRotator(angle: Double) { service.moveTo(device.number, angle).doRequest() diff --git a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/wheels/ASCOMFilterWheel.kt b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/wheels/ASCOMFilterWheel.kt index dc2035dde..d327dcc32 100644 --- a/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/wheels/ASCOMFilterWheel.kt +++ b/nebulosa-alpaca-indi/src/main/kotlin/nebulosa/alpaca/indi/device/wheels/ASCOMFilterWheel.kt @@ -29,7 +29,7 @@ data class ASCOMFilterWheel( processNames() } - override fun onDisconnected() {} + override fun onDisconnected() = Unit override fun moveTo(position: Int) { if (position in 1..count && position != this.position) { @@ -55,9 +55,9 @@ data class ASCOMFilterWheel( sender.fireOnEventReceived(FilterWheelNamesChanged(this)) } - override fun snoop(devices: Iterable) {} + override fun snoop(devices: Iterable) = Unit - override fun handleMessage(message: INDIProtocol) {} + override fun handleMessage(message: INDIProtocol) = Unit private fun processPosition() { service.position(device.number).doRequest { diff --git a/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/IdentityGuideAlgorithm.kt b/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/IdentityGuideAlgorithm.kt index 61e7d8a0c..f7fae575e 100644 --- a/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/IdentityGuideAlgorithm.kt +++ b/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/IdentityGuideAlgorithm.kt @@ -6,5 +6,5 @@ class IdentityGuideAlgorithm(override val axis: GuideAxis) : GuideAlgorithm { override fun compute(input: Double) = input - override fun reset() {} + override fun reset() = Unit } diff --git a/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/RandomDither.kt b/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/RandomDither.kt index c24877604..e4fb17c55 100644 --- a/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/RandomDither.kt +++ b/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/RandomDither.kt @@ -10,7 +10,7 @@ class RandomDither(private val random: Random = Random.Default) : Dither { return doubleArrayOf(ra, dec) } - override fun reset() {} + override fun reset() = Unit companion object { diff --git a/nebulosa-skycatalog/src/main/kotlin/nebulosa/skycatalog/SkyCatalog.kt b/nebulosa-skycatalog/src/main/kotlin/nebulosa/skycatalog/SkyCatalog.kt index dc52ca6fc..66b6f0157 100644 --- a/nebulosa-skycatalog/src/main/kotlin/nebulosa/skycatalog/SkyCatalog.kt +++ b/nebulosa-skycatalog/src/main/kotlin/nebulosa/skycatalog/SkyCatalog.kt @@ -39,7 +39,7 @@ abstract class SkyCatalog(estimatedSize: Int = 0) : Collection return res } - protected fun notifyLoadFinished() {} + protected fun notifyLoadFinished() = Unit override val size get() = data.size