Skip to content

Commit

Permalink
[api][desktop]: Mount
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Aug 5, 2023
1 parent 68ff394 commit e2d0582
Show file tree
Hide file tree
Showing 30 changed files with 745 additions and 308 deletions.
206 changes: 103 additions & 103 deletions api/objectbox-models/default.json

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions api/src/main/kotlin/nebulosa/api/controllers/MountController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ class MountController(
mountService.abort(mount)
}

@PostMapping("mountTrackingMode")
fun trackingMode(
@PostMapping("mountTrackMode")
fun trackMode(
@RequestParam @Valid @NotBlank name: String,
@RequestParam mode: TrackMode,
) {
val mount = requireNotNull(equipmentService.mount(name))
mountService.trackingMode(mount, mode)
mountService.trackMode(mount, mode)
}

@PostMapping("mountSlewRate")
Expand Down Expand Up @@ -156,6 +156,18 @@ class MountController(
mountService.moveEast(mount, enable)
}

@PostMapping("mountPark")
fun park(@RequestParam @Valid @NotBlank name: String) {
val mount = requireNotNull(equipmentService.mount(name))
mountService.park(mount)
}

@PostMapping("mountUnpark")
fun unpark(@RequestParam @Valid @NotBlank name: String) {
val mount = requireNotNull(equipmentService.mount(name))
mountService.unpark(mount)
}

@PostMapping("mountCoordinates")
fun coordinates(
@RequestParam @Valid @NotBlank name: String,
Expand Down Expand Up @@ -187,13 +199,14 @@ class MountController(
@RequestParam(required = false, defaultValue = "false") j2000: Boolean,
@RequestParam(required = false, defaultValue = "true") equatorial: Boolean,
@RequestParam(required = false, defaultValue = "true") horizontal: Boolean,
@RequestParam(required = false, defaultValue = "true") meridian: Boolean,
): ComputedCoordinateResponse {
val mount = requireNotNull(equipmentService.mount(name))
return mountService.computeCoordinates(
mount,
Angle.from(rightAscension, true, defaultValue = mount.rightAscension),
Angle.from(declination, defaultValue = mount.declination),
j2000, equatorial, horizontal,
j2000, equatorial, horizontal, meridian,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.objectbox.annotation.Index
@Entity
data class SavedCameraImageEntity(
@Id var id: Long = 0,
@Index var name: String = "",
@Index var camera: String = "",
@Index var path: String = "",
var width: Int = 0,
var height: Int = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ data class ComputedCoordinateResponse(
val azimuth: String,
val altitude: String,
val constellation: Constellation,
val lst: String,
val meridianAt: String,
val timeLeftToMeridianFlip: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class FocuserResponse(
val canReverse: Boolean,
val reverse: Boolean,
val canSync: Boolean,
val hasBackslash: Boolean,
val hasBacklash: Boolean,
val maxPosition: Int,
val hasThermometer: Boolean,
val temperature: Double,
Expand All @@ -30,7 +30,7 @@ data class FocuserResponse(
focuser.canReverse,
focuser.reverse,
focuser.canSync,
focuser.hasBackslash,
focuser.hasBacklash,
focuser.maxPosition,
focuser.hasThermometer,
focuser.temperature,
Expand Down
27 changes: 26 additions & 1 deletion api/src/main/kotlin/nebulosa/api/data/responses/MountResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nebulosa.api.data.responses

import nebulosa.indi.device.mount.*
import nebulosa.math.AngleFormatter
import java.time.ZoneOffset

data class MountResponse(
val name: String,
Expand All @@ -22,9 +23,21 @@ data class MountResponse(
val guideRateNS: Double,
val rightAscension: String,
val declination: String,
val canPulseGuide: Boolean,
val pulseGuiding: Boolean,
val canPark: Boolean,
val parking: Boolean,
val parked: Boolean,
val hasGPS: Boolean,
val longitude: Double,
val latitude: Double,
val elevation: Double,
val dateTime: Long,
val offsetInMinutes: Int,
val computedCoordinates: ComputedCoordinateResponse?,
) {

constructor(mount: Mount) : this(
constructor(mount: Mount, computedCoordinates: ComputedCoordinateResponse? = null) : this(
mount.name,
mount.connected,
mount.slewing,
Expand All @@ -43,5 +56,17 @@ data class MountResponse(
mount.guideRateNS,
mount.rightAscension.format(AngleFormatter.HMS),
mount.declination.format(AngleFormatter.SIGNED_DMS),
mount.canPulseGuide,
mount.pulseGuiding,
mount.canPark,
mount.parking,
mount.parked,
mount.hasGPS,
mount.longitude.degrees,
mount.latitude.degrees,
mount.elevation.meters,
mount.dateTime.toLocalDateTime().toInstant(ZoneOffset.UTC).toEpochMilli(),
mount.dateTime.offset.totalSeconds / 60,
computedCoordinates,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ class SavedCameraImageRepository(boxStore: BoxStore) : BoxRepository<SavedCamera

override val box = boxStore.boxFor(SavedCameraImageEntity::class.java)!!

fun withName(name: String): List<SavedCameraImageEntity> {
fun withCamera(camera: String): List<SavedCameraImageEntity> {
return box.query()
.equal(SavedCameraImageEntity_.name, name, QueryBuilder.StringOrder.CASE_SENSITIVE)
.equal(SavedCameraImageEntity_.camera, camera, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build().use { it.find() }
}

fun withNameLatest(name: String): SavedCameraImageEntity? {
fun withCameraLatest(camera: String): SavedCameraImageEntity? {
return box.query()
.equal(SavedCameraImageEntity_.name, name, QueryBuilder.StringOrder.CASE_SENSITIVE)
.equal(SavedCameraImageEntity_.camera, camera, QueryBuilder.StringOrder.CASE_SENSITIVE)
.orderDesc(SavedCameraImageEntity_.savedAt)
.build().use { it.findFirst() }
}

fun withNameAndPath(name: String, path: String): SavedCameraImageEntity? {
fun withCameraAndPath(camera: String, path: String): SavedCameraImageEntity? {
return box.query()
.equal(SavedCameraImageEntity_.name, name, QueryBuilder.StringOrder.CASE_SENSITIVE)
.equal(SavedCameraImageEntity_.camera, camera, QueryBuilder.StringOrder.CASE_SENSITIVE)
.and()
.equal(SavedCameraImageEntity_.path, path, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build().use { it.findFirst() }
Expand Down
6 changes: 3 additions & 3 deletions api/src/main/kotlin/nebulosa/api/services/ImageService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class ImageService(
val savedImage = savedCameraImageRepository.withPath("$path")

val info = ImageInfoResponse(
savedImage?.name ?: "",
savedImage?.camera ?: "",
savedImage?.path ?: "",
savedImage?.savedAt ?: 0L,
transformedImage.width,
Expand Down Expand Up @@ -125,11 +125,11 @@ class ImageService(
}

fun imagesOfCamera(name: String): List<SavedCameraImageEntity> {
return savedCameraImageRepository.withName(name)
return savedCameraImageRepository.withCamera(name)
}

fun latestImageOfCamera(name: String): SavedCameraImageEntity {
return savedCameraImageRepository.withNameLatest(name)!!
return savedCameraImageRepository.withCameraLatest(name)!!
}

fun savedImageOfPath(path: Path): SavedCameraImageEntity {
Expand Down
73 changes: 64 additions & 9 deletions api/src/main/kotlin/nebulosa/api/services/MountService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package nebulosa.api.services

import jakarta.annotation.PostConstruct
import nebulosa.api.data.responses.ComputedCoordinateResponse
import nebulosa.constants.PI
import nebulosa.constants.TAU
import nebulosa.indi.device.PropertyChangedEvent
import nebulosa.indi.device.mount.*
import nebulosa.math.Angle
Expand All @@ -16,15 +18,29 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter

@Service
class MountService(
private val webSocketService: WebSocketService,
private val eventBus: EventBus,
) {

private val centerPosition = HashMap<Mount, GeographicPosition>(2)
@Volatile private var prevTime = 0L
private val site = HashMap<Mount, GeographicPosition>(2)

private var currentTime = UTC.now()
@Synchronized get() {
val curTime = System.currentTimeMillis()

if (curTime - prevTime >= 60000L) {
field = UTC.now()
}

return field
}

@PostConstruct
private fun initialize() {
Expand All @@ -41,7 +57,7 @@ class MountService(

if (event is MountGeographicCoordinateChanged) {
val site = Geoid.IERS2010.latLon(event.device.longitude, event.device.latitude, event.device.elevation)
centerPosition[event.device] = site
this.site[event.device] = site
}
}

Expand Down Expand Up @@ -80,8 +96,8 @@ class MountService(
mount.abortMotion()
}

fun trackingMode(mount: Mount, mode: TrackMode) {
mount.trackingMode(mode)
fun trackMode(mount: Mount, mode: TrackMode) {
mount.trackMode(mode)
}

fun slewRate(mount: Mount, rate: SlewRate) {
Expand All @@ -104,6 +120,20 @@ class MountService(
mount.moveEast(enable)
}

fun park(mount: Mount) {
mount.park()
}

fun unpark(mount: Mount) {
mount.unpark()
}

private fun computeTimeLeftToMeridianFlip(rightAscension: Angle, lst: Angle): Angle {
val timeLeft = rightAscension - lst
return if (timeLeft.value < 0.0) timeLeft - SIDEREAL_TIME_DIFF * (timeLeft.normalized.value / TAU)
else timeLeft + SIDEREAL_TIME_DIFF * (1.0 - timeLeft.value / TAU)
}

fun coordinates(mount: Mount, longitude: Angle, latitude: Angle, elevation: Distance) {
mount.coordinates(longitude, latitude, elevation)
}
Expand All @@ -117,11 +147,10 @@ class MountService(
mount: Mount,
rightAscension: Angle = mount.rightAscension, declination: Angle = mount.declination,
j2000: Boolean,
equatorial: Boolean,
horizontal: Boolean,
equatorial: Boolean, horizontal: Boolean, meridian: Boolean,
): ComputedCoordinateResponse {
val center = centerPosition[mount]!!
val time = UTC.now()
val center = site[mount]!!
val time = currentTime
val epoch = if (j2000) null else time

val icrf = ICRF.equatorial(rightAscension, declination, time = time, epoch = epoch, center = center)
Expand All @@ -144,6 +173,32 @@ class MountService(
altitude = altAz.latitude.format(AngleFormatter.SIGNED_DMS)
}

return ComputedCoordinateResponse(rightAscension, declination, azimuth, altitude, constellation)
var meridianAt = ""
var timeLeftToMeridianFlip = ""
var lst = ""

if (meridian) {
val lst = site[mount]!!.lstAt(currentTime).also { lst = it.format(LST_FORMAT) }
val timeLeftToMeridianFlip = computeTimeLeftToMeridianFlip(mount.rightAscension, lst)
.also { timeLeftToMeridianFlip = it.format(LST_FORMAT) }
meridianAt = LocalDateTime.now().plusSeconds((timeLeftToMeridianFlip.hours * 3600.0).toLong()).format(MERIDIAN_TIME_FORMAT)
}

return ComputedCoordinateResponse(
rightAscension, declination, azimuth, altitude,
constellation, lst, meridianAt, timeLeftToMeridianFlip,
)
}

companion object {

private const val SIDEREAL_TIME_DIFF = 0.06552777 * PI / 12.0

@JvmStatic private val MERIDIAN_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss")
@JvmStatic private val LST_FORMAT = AngleFormatter.Builder()
.hours()
.noSign()
.secondsDecimalPlaces(0)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ abstract class CachedEphemerisProvider<T : Any> : EphemerisProvider<T> {

val elements = compute(key.first, key.second, startTime, endTime)
val cachedElements = ephemeris.getOrPut(key) { HashMap(1441) }
elements.forEach { cachedElements[it.time] = it }
elements.forEach { cachedElements[it.dateTime] = it }
return elements
}

Expand Down
2 changes: 2 additions & 0 deletions desktop/app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ let selectedFilterWheel: FilterWheel
const args = process.argv.slice(1)
const serve = args.some(e => e === '--serve')

app.commandLine.appendSwitch('disable-http-cache')

function createMainWindow() {
createWindow({ id: 'home', path: 'home' })

Expand Down
Binary file modified desktop/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
"scripts": {
"postinstall": "electron-builder install-app-deps",
"ng": "ng",
"start": "node copyFiles.js && npm-run-all -p electron:serve ng:serve",
"copy:files": "node copyFiles.js",
"start": "npm run copy:files && npm-run-all -p electron:serve ng:serve",
"ng:serve": "ng serve -c web --hmr",
"build": "node copyFiles.js && npm run electron:serve-tsc && ng build --base-href ./",
"build": "npm run copy:files && npm run electron:serve-tsc && ng build --base-href ./",
"build:dev": "npm run build -- -c dev",
"build:prod": "npm run build -- -c production",
"web:build": "npm run build -- -c web-production",
Expand Down
14 changes: 14 additions & 0 deletions desktop/src/app/atlas/atlas.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MoonComponent } from '../../shared/components/moon/moon.component'
import { ApiService } from '../../shared/services/api.service'
import { BrowserWindowService } from '../../shared/services/browser-window.service'
import { CONSTELLATIONS, Constellation, DeepSkyObject, EMPTY_BODY_POSITION, EMPTY_LOCATION, Location, MinorPlanet, SkyObjectType, Star, TypeWithAll } from '../../shared/types'
import { ElectronService } from '../../shared/services/electron.service'

export interface PlanetItem {
name: string
Expand Down Expand Up @@ -56,14 +57,26 @@ export class AtlasComponent implements AfterViewInit, OnDestroy {
{
icon: 'mdi mdi-check',
label: 'Go To',
command: async () => {
const mount = await this.electron.sendSync('SELECTED_MOUNT')
this.api.mountGoTo(mount, this.bodyPosition.rightAscension, this.bodyPosition.declination, false)
},
},
{
icon: 'mdi mdi-check',
label: 'Slew To',
command: async () => {
const mount = await this.electron.sendSync('SELECTED_MOUNT')
this.api.mountSlewTo(mount, this.bodyPosition.rightAscension, this.bodyPosition.declination, false)
},
},
{
icon: 'mdi mdi-sync',
label: 'Sync',
command: async () => {
const mount = await this.electron.sendSync('SELECTED_MOUNT')
this.api.mountSync(mount, this.bodyPosition.rightAscension, this.bodyPosition.declination, false)
},
},
{
icon: 'mdi mdi-image',
Expand Down Expand Up @@ -428,6 +441,7 @@ export class AtlasComponent implements AfterViewInit, OnDestroy {
private title: Title,
private api: ApiService,
private browserWindow: BrowserWindowService,
private electron: ElectronService,
) {
title.setTitle('Sky Atlas')

Expand Down
Loading

0 comments on commit e2d0582

Please sign in to comment.