diff --git a/api/src/main/kotlin/nebulosa/api/Nebulosa.kt b/api/src/main/kotlin/nebulosa/api/Nebulosa.kt index 16f54db7a..be6e4ccb6 100644 --- a/api/src/main/kotlin/nebulosa/api/Nebulosa.kt +++ b/api/src/main/kotlin/nebulosa/api/Nebulosa.kt @@ -1,6 +1,7 @@ package nebulosa.api import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jsonMapper import com.github.rvesse.airline.annotations.Command @@ -16,6 +17,7 @@ import org.koin.core.context.startKoin import java.nio.file.Path import java.time.LocalDate import java.time.LocalTime +import java.util.concurrent.ConcurrentHashMap @Command(name = "nebulosa") class Nebulosa : Runnable, AutoCloseable { @@ -63,8 +65,11 @@ class Nebulosa : Runnable, AutoCloseable { private data object LocationConverter : (String) -> Location? { + private val CACHED_LOCATION = ConcurrentHashMap(4) + override fun invoke(value: String): Location? { - return if (value.isBlank()) null else OBJECT_MAPPER.readValue(value, Location::class.java) + return if (value.isBlank()) null + else CACHED_LOCATION.computeIfAbsent(value) { OBJECT_MAPPER.readValue(value, Location::class.java) } } } @@ -92,6 +97,7 @@ class Nebulosa : Runnable, AutoCloseable { addModule(DeviceModule()) disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) } } } diff --git a/api/src/main/kotlin/nebulosa/api/atlas/Location.kt b/api/src/main/kotlin/nebulosa/api/atlas/Location.kt index 8bd3b0ca3..f840efa50 100644 --- a/api/src/main/kotlin/nebulosa/api/atlas/Location.kt +++ b/api/src/main/kotlin/nebulosa/api/atlas/Location.kt @@ -1,5 +1,6 @@ package nebulosa.api.atlas +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize import nebulosa.api.beans.converters.angle.DegreesDeserializer @@ -18,5 +19,6 @@ data class Location( @JvmField val offsetInMinutes: Int = 0, ) : GeographicCoordinate, TimeZonedInSeconds { - override val offsetInSeconds = offsetInMinutes * 60 + override val offsetInSeconds + @JsonIgnore get() = offsetInMinutes * 60 } diff --git a/api/src/main/kotlin/nebulosa/api/atlas/SkyAtlasController.kt b/api/src/main/kotlin/nebulosa/api/atlas/SkyAtlasController.kt index a65bb5cc6..4193c090d 100644 --- a/api/src/main/kotlin/nebulosa/api/atlas/SkyAtlasController.kt +++ b/api/src/main/kotlin/nebulosa/api/atlas/SkyAtlasController.kt @@ -28,7 +28,7 @@ class SkyAtlasController( app.get("sky-atlas/planets/{code}/position", ::positionOfPlanet) app.get("sky-atlas/planets/{code}/altitude-points", ::altitudePointsOfPlanet) app.get("sky-atlas/minor-planets", ::searchMinorPlanet) - app.get("sky-atlas/close-approaches", ::closeApproachesForMinorPlanets) + app.get("sky-atlas/minor-planets/close-approaches", ::closeApproachesForMinorPlanets) app.get("sky-atlas/sky-objects", ::searchSkyObject) app.get("sky-atlas/sky-objects/types", ::skyObjectTypes) app.get("sky-atlas/sky-objects/{id}/position", ::positionOfSkyObject) @@ -72,12 +72,12 @@ class SkyAtlasController( ctx.json(skyAtlasService.altitudePointsOfMoon(location, dateTime, stepSize, fast)) } - private fun positionOfPlanet(ctx: Context): BodyPosition { + private fun positionOfPlanet(ctx: Context) { val code = ctx.pathParam("code") val location = ctx.location() val dateTime = LocalDateTime.of(ctx.localDate(), ctx.localTime()) val fast = ctx.queryParamAsBoolean("fast").getOrDefault(false) - return skyAtlasService.positionOfPlanet(location, code, dateTime, fast) + ctx.json(skyAtlasService.positionOfPlanet(location, code, dateTime, fast)) } private fun altitudePointsOfPlanet(ctx: Context) { @@ -89,9 +89,9 @@ class SkyAtlasController( ctx.json(skyAtlasService.altitudePointsOfPlanet(location, code, dateTime, stepSize, fast)) } - private fun searchMinorPlanet(ctx: Context): MinorPlanet { + private fun searchMinorPlanet(ctx: Context) { val text = ctx.queryParamAsString("text").notBlank().get() - return skyAtlasService.searchMinorPlanet(text) + ctx.json(skyAtlasService.searchMinorPlanet(text)) } private fun closeApproachesForMinorPlanets(ctx: Context) { @@ -116,7 +116,7 @@ class SkyAtlasController( ctx.json(skyAtlasService.altitudePointsOfSkyObject(location, id, dateTime, stepSize)) } - private fun searchSkyObject(ctx: Context): List { + private fun searchSkyObject(ctx: Context) { val text = ctx.queryParamAsString("text").getOrDefault("") val rightAscension = ctx.queryParamAsString("rightAscension").getOrDefault("") val declination = ctx.queryParamAsString("declination").getOrDefault("") @@ -127,10 +127,12 @@ class SkyAtlasController( val type = ctx.queryParamAsString("type").allowNullable().get()?.let(SkyObjectType::valueOf) val id = ctx.queryParamAsLong("id").getOrDefault(0L) - return skyAtlasService.searchSkyObject( + val result = skyAtlasService.searchSkyObject( text, rightAscension.hours, declination.deg, radius.deg, constellation, magnitudeMin, magnitudeMax, type, id, ) + + ctx.json(result) } private fun skyObjectTypes(ctx: Context) { diff --git a/api/src/main/kotlin/nebulosa/api/image/ImageController.kt b/api/src/main/kotlin/nebulosa/api/image/ImageController.kt index 4d6884303..510190d14 100644 --- a/api/src/main/kotlin/nebulosa/api/image/ImageController.kt +++ b/api/src/main/kotlin/nebulosa/api/image/ImageController.kt @@ -46,7 +46,7 @@ class ImageController( val path = ctx.queryParamAsPath("path").exists().get() val request = ctx.bodyAsClass() val location = ctx.locationOrNull() - imageService.annotations(path, request, location) + ctx.json(imageService.annotations(path, request, location)) } private fun coordinateInterpolation(ctx: Context) { @@ -57,7 +57,7 @@ class ImageController( private fun histogram(ctx: Context) { val path = ctx.queryParamAsPath("path").exists().get() val bitLength = ctx.queryParamAsInt("bitLength").range(8..16).getOrDefault(16) - imageService.histogram(path, bitLength) + ctx.json(imageService.histogram(path, bitLength)) } private fun fovCameras(ctx: Context) { diff --git a/api/src/main/kotlin/nebulosa/api/image/ImageService.kt b/api/src/main/kotlin/nebulosa/api/image/ImageService.kt index fcf78e45f..f37b5f051 100644 --- a/api/src/main/kotlin/nebulosa/api/image/ImageService.kt +++ b/api/src/main/kotlin/nebulosa/api/image/ImageService.kt @@ -218,11 +218,14 @@ class ImageService( val rightAscension = it[1].hours.takeIf(Angle::isFinite) ?: return@forEach val declination = it[2].deg.takeIf(Angle::isFinite) ?: return@forEach val (x, y) = wcs.skyToPix(rightAscension, declination) - val magnitude = it[6].replace(INVALID_MAG_CHARS, "").toDoubleOrNull() ?: SkyObject.UNKNOWN_MAGNITUDE - val minorPlanet = ImageAnnotation.MinorPlanet(0L, it[0], rightAscension, declination, magnitude) - val annotation = ImageAnnotation(x, y, minorPlanet = minorPlanet) - annotations.add(annotation) - count++ + + if (x >= 0 && y >= 0 && x < image.width && y < image.height) { + val magnitude = it[6].replace(INVALID_MAG_CHARS, "").toDoubleOrNull() ?: SkyObject.UNKNOWN_MAGNITUDE + val minorPlanet = ImageAnnotation.MinorPlanet(0L, it[0], rightAscension, declination, magnitude) + val annotation = ImageAnnotation(x, y, minorPlanet = minorPlanet) + annotations.add(annotation) + count++ + } } } @@ -255,10 +258,13 @@ class ImageService( val astrometric = barycentric.observe(entry).equatorial() 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)) - annotations.add(annotation) - count++ + + if (x >= 0 && y >= 0 && x < image.width && y < image.height) { + val annotation = if (entry.type.classification == ClassificationType.STAR) ImageAnnotation(x, y, star = StarDSO(entry)) + else ImageAnnotation(x, y, dso = StarDSO(entry)) + annotations.add(annotation) + count++ + } } LOG.info("found {} stars/DSOs", count) diff --git a/api/src/main/kotlin/nebulosa/api/mounts/MountController.kt b/api/src/main/kotlin/nebulosa/api/mounts/MountController.kt index 1ea1ab1fe..0734ed7d3 100644 --- a/api/src/main/kotlin/nebulosa/api/mounts/MountController.kt +++ b/api/src/main/kotlin/nebulosa/api/mounts/MountController.kt @@ -21,29 +21,29 @@ class MountController( init { app.get("mounts", ::mounts) - app.get("mount/{id}", ::mount) - app.put("mount/{id}/connect", ::connect) - app.put("mount/{id}/disconnect", ::disconnect) - app.put("mount/{id}/tracking", ::tracking) - app.put("mount/{id}/sync", ::sync) - app.put("mount/{id}/slew", ::slew) - app.put("mount/{id}/goto", ::goTo) - app.put("mount/{id}/home", ::home) - app.put("mount/{id}/abort", ::abort) - app.put("mount/{id}/track-mode", ::trackMode) - app.put("mount/{id}/slew-rate", ::slewRate) - app.put("mount/{id}/move", ::move) - app.put("mount/{id}/park", ::park) - app.put("mount/{id}/unpark", ::unpark) - app.put("mount/{id}/coordinates", ::coordinates) - app.put("mount/{id}/datetime", ::dateTime) - app.get("mount/{id}/location", ::location) - app.get("mount/{id}/location/{type}", ::celestialLocation) - app.put("mount/{id}/point-here", ::pointMountHere) - app.get("mount/{id}/remote-control", ::remoteControlList) - app.put("mount/{id}/remote-control/start", ::remoteControlStart) - app.put("mount/{id}/remote-control/stop", ::remoteControlStop) - app.put("mount/{id}/listen", ::listen) + app.get("mounts/{id}", ::mount) + app.put("mounts/{id}/connect", ::connect) + app.put("mounts/{id}/disconnect", ::disconnect) + app.put("mounts/{id}/tracking", ::tracking) + app.put("mounts/{id}/sync", ::sync) + app.put("mounts/{id}/slew", ::slew) + app.put("mounts/{id}/goto", ::goTo) + app.put("mounts/{id}/home", ::home) + app.put("mounts/{id}/abort", ::abort) + app.put("mounts/{id}/track-mode", ::trackMode) + app.put("mounts/{id}/slew-rate", ::slewRate) + app.put("mounts/{id}/move", ::move) + app.put("mounts/{id}/park", ::park) + app.put("mounts/{id}/unpark", ::unpark) + app.put("mounts/{id}/coordinates", ::coordinates) + app.put("mounts/{id}/datetime", ::dateTime) + app.get("mounts/{id}/location", ::location) + app.get("mounts/{id}/location/{type}", ::celestialLocation) + app.put("mounts/{id}/point-here", ::pointMountHere) + app.get("mounts/{id}/remote-control", ::remoteControlList) + app.put("mounts/{id}/remote-control/start", ::remoteControlStart) + app.put("mounts/{id}/remote-control/stop", ::remoteControlStop) + app.put("mounts/{id}/listen", ::listen) } private fun mounts(ctx: Context) { @@ -125,7 +125,7 @@ class MountController( private fun slewRate(ctx: Context) { val id = ctx.pathParam("id") val mount = connectionService.mount(id) ?: return - val rate = ctx.queryParamAsString("mode").notBlank().get() + val rate = ctx.queryParamAsString("rate").notBlank().get() mountService.slewRate(mount, mount.slewRates.first { it.name == rate }) } @@ -208,7 +208,7 @@ class MountController( private fun remoteControlStart(ctx: Context) { val id = ctx.pathParam("id") val mount = connectionService.mount(id) ?: return - val protocol = ctx.queryParamAsString("procotol").notBlank().get().let(MountRemoteControlProtocol::valueOf) + val protocol = ctx.queryParamAsString("protocol").notBlank().get().let(MountRemoteControlProtocol::valueOf) val host = ctx.queryParamAsString("host").getOrDefault("0.0.0.0") val port = ctx.queryParamAsInt("port").positive().getOrDefault(10001) mountService.remoteControlStart(mount, protocol, host, port) @@ -217,7 +217,7 @@ class MountController( private fun remoteControlStop(ctx: Context) { val id = ctx.pathParam("id") val mount = connectionService.mount(id) ?: return - val protocol = ctx.queryParamAsString("procotol").notBlank().get().let(MountRemoteControlProtocol::valueOf) + val protocol = ctx.queryParamAsString("protocol").notBlank().get().let(MountRemoteControlProtocol::valueOf) mountService.remoteControlStop(mount, protocol) } diff --git a/api/src/main/kotlin/nebulosa/api/platesolver/PlateSolverController.kt b/api/src/main/kotlin/nebulosa/api/platesolver/PlateSolverController.kt index 5d8db127d..3c954544f 100644 --- a/api/src/main/kotlin/nebulosa/api/platesolver/PlateSolverController.kt +++ b/api/src/main/kotlin/nebulosa/api/platesolver/PlateSolverController.kt @@ -20,7 +20,7 @@ class PlateSolverController( private fun start(ctx: Context) { val path = ctx.queryParamAsPath("path").exists().get() val solver = ctx.bodyValidator().validate().get() - plateSolverService.solveImage(solver, path) + ctx.json(plateSolverService.solveImage(solver, path)) } @Suppress("UNUSED_PARAMETER") diff --git a/desktop/app/window.manager.ts b/desktop/app/window.manager.ts index 7e834ff06..ea7863aad 100644 --- a/desktop/app/window.manager.ts +++ b/desktop/app/window.manager.ts @@ -209,16 +209,16 @@ export class WindowManager { } } - private createWebSocket(host: string, port: number, appWindow: ApplicationWindow) { + private createWebSocket(host: string, port: number, connected: (webSocket: WebSocket) => void) { const webSocket = new WebSocket(`ws://${host}:${port}/ws`) const reconnect = () => { - setTimeout(() => this.createWebSocket(host, port, appWindow), 2000) + setTimeout(() => this.createWebSocket(host, port, connected), 2000) } webSocket.on('open', () => { console.info('Web Socket connected') - appWindow.webSocket = webSocket + connected(webSocket) }) webSocket.on('message', (data: Buffer) => { @@ -254,7 +254,7 @@ export class WindowManager { const open: OpenWindow = { id: 'home', path: 'home', preference: {} } const appWindow = await this.createWindow(open) - this.createWebSocket(host, port, appWindow) + this.createWebSocket(host, port, (webSocket) => (appWindow.webSocket = webSocket)) appWindow.apiProcess = apiProcess } diff --git a/desktop/src/app/filterwheel/filterwheel.component.ts b/desktop/src/app/filterwheel/filterwheel.component.ts index 1cc4c9bbf..c5866a8d1 100644 --- a/desktop/src/app/filterwheel/filterwheel.component.ts +++ b/desktop/src/app/filterwheel/filterwheel.component.ts @@ -247,7 +247,7 @@ export class FilterWheelComponent implements AfterContentInit, OnDestroy, Tickab const offset = nextFocusOffset - currentFocusOffset - if (this.focuser && offset !== 0) { + if (this.focuser?.connected && offset !== 0) { if (offset < 0) await this.api.focuserMoveIn(this.focuser, -offset) else await this.api.focuserMoveOut(this.focuser, offset) } diff --git a/desktop/src/app/mount/mount.component.html b/desktop/src/app/mount/mount.component.html index 69e96a25d..7f8cf302a 100644 --- a/desktop/src/app/mount/mount.component.html +++ b/desktop/src/app/mount/mount.component.html @@ -476,7 +476,7 @@ severity="danger" pTooltip="Stop" tooltipPosition="bottom" - (onClick)="stopRemoteControl(item.type)" /> + (onClick)="stopRemoteControl(item.protocol)" /> diff --git a/desktop/src/shared/interceptors/confirmation.interceptor.ts b/desktop/src/shared/interceptors/confirmation.interceptor.ts index ec5be3093..3fe7e2bae 100644 --- a/desktop/src/shared/interceptors/confirmation.interceptor.ts +++ b/desktop/src/shared/interceptors/confirmation.interceptor.ts @@ -1,6 +1,6 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' import { Injectable } from '@angular/core' -import { Observable, finalize } from 'rxjs' +import { Observable } from 'rxjs' import { ConfirmationService } from '../services/confirmation.service' import { IdempotencyKeyInterceptor } from './idempotency-key.interceptor' @@ -17,20 +17,8 @@ export class ConfirmationInterceptor implements HttpInterceptor { if (idempotencyKey) { this.confirmationService.register(idempotencyKey) } - - const res = next.handle(req) - - if (idempotencyKey) { - return res.pipe( - finalize(() => { - this.confirmationService.unregister(idempotencyKey) - }), - ) - } - - return res - } else { - return next.handle(req) } + + return next.handle(req) } } diff --git a/desktop/src/shared/services/confirmation.service.ts b/desktop/src/shared/services/confirmation.service.ts index 55b4c0b12..509c5a14b 100644 --- a/desktop/src/shared/services/confirmation.service.ts +++ b/desktop/src/shared/services/confirmation.service.ts @@ -6,7 +6,7 @@ import { ApiService } from './api.service' @Injectable({ providedIn: 'root' }) export class ConfirmationService { - private readonly keys = new Map() + private readonly keys = new Set() constructor( private readonly angularService: AngularService, @@ -14,11 +14,7 @@ export class ConfirmationService { ) {} register(key: string) { - this.keys.set(key, '') - } - - unregister(key: string) { - this.keys.delete(key) + this.keys.add(key) } has(key: string) { @@ -28,6 +24,5 @@ export class ConfirmationService { async processConfirmationEvent(event: ConfirmationEvent) { const response = await this.angularService.confirm(event.message) await this.api.confirm(event.idempotencyKey, response === ConfirmEventType.ACCEPT) - this.unregister(event.idempotencyKey) } }