diff --git a/kstars/CMakeLists.txt b/kstars/CMakeLists.txt index 5d990794d..503edb7e7 100644 --- a/kstars/CMakeLists.txt +++ b/kstars/CMakeLists.txt @@ -1,1218 +1,1219 @@ add_subdirectory( data ) add_subdirectory( icons ) add_subdirectory( htmesh ) if (${KF5_VERSION} VERSION_EQUAL 5.18.0 OR ${KF5_VERSION} VERSION_GREATER 5.18.0) SET(HAVE_KF5WIT 1) # if(NOT BUILD_KSTARS_LITE) # add_subdirectory( tools/whatsinteresting/qml) # endif(NOT BUILD_KSTARS_LITE) else() SET(HAVE_KF5WIT 0) endif() if (ANDROID AND CMAKE_TOOLCHAIN_FILE) include(${CMAKE_TOOLCHAIN_FILE}) endif () if (NOT ANDROID) find_package(ZLIB REQUIRED) find_package(Threads REQUIRED) endif () if(MSVC) add_definitions(-D_USE_MATH_DEFINES=1) add_definitions(-DNOMINMAX) endif() include_directories( ${kstars_SOURCE_DIR}/kstars ${kstars_SOURCE_DIR}/kstars/skyobjects ${kstars_SOURCE_DIR}/kstars/skycomponents ${kstars_SOURCE_DIR}/kstars/auxiliary ${kstars_SOURCE_DIR}/kstars/time ${kstars_SOURCE_DIR}/kstars/tools ) if (INDI_FOUND) if(BUILD_KSTARS_LITE) set (fits_klite_SRCS fitsviewer/fitsdata.cpp ) set (fits2_klite_SRCS fitsviewer/bayer.c fitsviewer/fpack.c fitsviewer/fpackutil.c ) include_directories(${CFITSIO_INCLUDE_DIR}) include_directories(${NOVA_INCLUDE_DIR}) set (indi_klite_SRCS indi/clientmanagerlite.cpp indi/inditelescopelite.cpp kstarslite/skyitems/skynodes/crosshairnode.cpp kstarslite/skyitems/telescopesymbolsitem.cpp ) endif () set(indiui_SRCS indi/streamform.ui indi/drivermanager.ui indi/opsindi.ui indi/indihostconf.ui indi/customdrivers.ui #indi/telescopewizard.ui ) set(indi_SRCS indi/drivermanager.cpp indi/servermanager.cpp indi/clientmanager.cpp indi/blobmanager.cpp indi/guimanager.cpp indi/driverinfo.cpp indi/deviceinfo.cpp indi/indidevice.cpp indi/indigroup.cpp indi/indiproperty.cpp indi/indielement.cpp indi/indistd.cpp indi/indilistener.cpp indi/inditelescope.cpp indi/indiccd.cpp indi/wsmedia.cpp indi/indifocuser.cpp indi/indifilter.cpp indi/indidome.cpp indi/indiweather.cpp indi/indicap.cpp indi/indilightbox.cpp indi/indidbus.cpp indi/opsindi.cpp #indi/telescopewizardprocess.cpp indi/streamwg.cpp indi/videowg.cpp indi/indiwebmanager.cpp indi/customdrivers.cpp ) if (CFITSIO_FOUND) set(ekosui_SRCS ekos/opsekos.ui ekos/manager.ui ekos/profileeditor.ui ekos/profilewizard.ui # Scheduler ekos/scheduler/scheduler.ui ekos/scheduler/mosaic.ui # Capture ekos/capture/capture.ui ekos/capture/calibrationoptions.ui ekos/capture/dslrinfo.ui ekos/capture/rotatorsettings.ui ekos/capture/customproperties.ui # Align ekos/align/align.ui ekos/align/opsastrometry.ui ekos/align/opsalign.ui ekos/align/opsastrometrycfg.ui ekos/align/opsastrometryindexfiles.ui ekos/align/mountmodel.ui ekos/align/opsastap.ui # Focus ekos/focus/focus.ui # Mount ekos/mount/mount.ui # Guide ekos/guide/guide.ui ekos/guide/opscalibration.ui ekos/guide/opsguide.ui ekos/guide/manualdither.ui ekos/observatory/observatory.ui #TODO remove from GIT #ekos/guide/guider.ui #ekos/guide/rcalibration.ui # Auxiliary ekos/auxiliary/filtersettings.ui ekos/auxiliary/opslogs.ui ekos/auxiliary/serialportassistant.ui # Ekos Live ekos/ekoslive/ekoslivedialog.ui # INDI Hub ekos/indihub.ui ) set(ekos_SRCS ekos/ekos.cpp ekos/manager.cpp ekos/profileeditor.cpp ekos/profilewizard.cpp ekos/qMDNS.cpp ekos/opsekos.cpp # Auxiliary ekos/auxiliary/dome.cpp ekos/auxiliary/weather.cpp ekos/auxiliary/dustcap.cpp ekos/auxiliary/darklibrary.cpp ekos/auxiliary/filtermanager.cpp ekos/auxiliary/filterdelegate.cpp ekos/auxiliary/opslogs.cpp ekos/auxiliary/serialportassistant.cpp # Capture ekos/capture/capture.cpp ekos/capture/sequencejob.cpp ekos/capture/dslrinfodialog.cpp ekos/capture/rotatorsettings.cpp ekos/capture/customproperties.cpp # Scheduler ekos/scheduler/schedulerjob.cpp ekos/scheduler/scheduler.cpp ekos/scheduler/mosaic.cpp # Focus ekos/focus/focus.cpp ekos/focus/focusalgorithms.cpp ekos/focus/polynomialfit.cpp # Mount ekos/mount/mount.cpp # Align ekos/align/align.cpp ekos/align/alignview.cpp ekos/align/astrometryparser.cpp ekos/align/opsastrometry.cpp ekos/align/opsalign.cpp ekos/align/opsastrometrycfg.cpp ekos/align/opsastrometryindexfiles.cpp ekos/align/opsastap.cpp ekos/align/offlineastrometryparser.cpp ekos/align/onlineastrometryparser.cpp ekos/align/remoteastrometryparser.cpp ekos/align/astapastrometryparser.cpp # Guide ekos/guide/guide.cpp ekos/guide/guideinterface.cpp ekos/guide/opscalibration.cpp ekos/guide/opsguide.cpp # Internal Guide ekos/guide/internalguide/gmath.cpp ekos/guide/internalguide/internalguider.cpp #ekos/guide/internalguide/guider.cpp ekos/guide/internalguide/matr.cpp #ekos/guide/internalguide/rcalibration.cpp ekos/guide/internalguide/vect.cpp ekos/guide/internalguide/imageautoguiding.cpp + ekos/guide/internalguide/guidelog.cpp # External Guide ekos/guide/externalguide/phd2.cpp ekos/guide/externalguide/linguider.cpp #Observatory ekos/observatory/observatory.cpp ekos/observatory/observatorymodel.cpp ekos/observatory/observatorydomemodel.cpp ekos/observatory/observatoryweathermodel.cpp # Ekos Live ekos/ekoslive/ekosliveclient.cpp ekos/ekoslive/message.cpp ekos/ekoslive/media.cpp ekos/ekoslive/cloud.cpp ) endif(CFITSIO_FOUND) include_directories(${INDI_INCLUDE_DIR}) endif (INDI_FOUND) if (CFITSIO_FOUND) set (sep_SRCS fitsviewer/sep/analyse.c fitsviewer/sep/aperture.c fitsviewer/sep/background.c fitsviewer/sep/convolve.c fitsviewer/sep/deblend.c fitsviewer/sep/extract.c fitsviewer/sep/lutz.c fitsviewer/sep/util.c ) set (hough_SRCS fitsviewer/hough/houghline.cpp ) set (fits_SRCS fitsviewer/fitslabel.cpp fitsviewer/fitsviewer.cpp fitsviewer/stretch.cpp fitsviewer/fitstab.cpp fitsviewer/fitsdebayer.cpp fitsviewer/opsfits.cpp ) if (Qt5DataVisualization_FOUND) set(fits_SRCS ${fits_SRCS} fitsviewer/starprofileviewer.cpp) endif() set (fits2_SRCS fitsviewer/bayer.c fitsviewer/fpack.c fitsviewer/fpackutil.c fitsviewer/fitshistogram.cpp fitsviewer/fitsview.cpp fitsviewer/fitsdata.cpp fitsviewer/fitsstardetector.cpp fitsviewer/fitsthresholddetector.cpp fitsviewer/fitsgradientdetector.cpp fitsviewer/fitscentroiddetector.cpp fitsviewer/fitssepdetector.cpp fitsviewer/fitsbahtinovdetector.cpp fitsviewer/fitsskyobject.cpp ) set (fitsui_SRCS fitsviewer/fitsheaderdialog.ui fitsviewer/statform.ui fitsviewer/fitsdebayer.ui indi/streamform.ui indi/recordingoptions.ui fitsviewer/fitshistogramui.ui fitsviewer/opsfits.ui ) include_directories(${CFITSIO_INCLUDE_DIR}) endif(CFITSIO_FOUND) IF (CFITSIO_FOUND) IF (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) IF (SANITIZERS) SET_SOURCE_FILES_PROPERTIES(fitsviewer/bayer.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align -fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fitsdata.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fitshistogram.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fitsview.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/hough/houghline.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") ELSE () SET_SOURCE_FILES_PROPERTIES(fitsviewer/bayer.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") ENDIF () SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/analyse.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/aperture.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align -Wno-pointer-arith") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/background.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/deblend.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align -Wno-incompatible-pointer-types-discards-qualifiers") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/extract.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/lutz.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/util.c PROPERTIES COMPILE_FLAGS "-Wno-incompatible-pointer-types-discards-qualifiers") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpack.c PROPERTIES COMPILE_FLAGS "-Wno-error") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpackutil.c PROPERTIES COMPILE_FLAGS "-Wno-error") ELSEIF (NOT WIN32) SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpack.c PROPERTIES COMPILE_FLAGS "-Wno-error") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpackutil.c PROPERTIES COMPILE_FLAGS "-Wno-error") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/aperture.c PROPERTIES COMPILE_FLAGS "-Wno-pointer-arith") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/deblend.c PROPERTIES COMPILE_FLAGS "-Wno-discarded-qualifiers") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/util.c PROPERTIES COMPILE_FLAGS "-Wno-discarded-qualifiers") ENDIF () ENDIF () if(WCSLIB_FOUND) include_directories( ${WCSLIB_INCLUDE_DIR} ) endif(WCSLIB_FOUND) set(xplanet_SRCS xplanet/opsxplanet.cpp ) set(xplanetui_SRCS xplanet/opsxplanet.ui ) ########### next target ############### set(libkstarstools_SRCS tools/altvstime.cpp tools/avtplotwidget.cpp tools/calendarwidget.cpp tools/conjunctions.cpp tools/eclipsetool.cpp tools/eclipsehandler.cpp tools/eclipsetool/lunareclipsehandler.cpp tools/jmoontool.cpp tools/approachsolver.cpp tools/ksconjunct.cpp tools/eqplotwidget.cpp tools/astrocalc.cpp tools/modcalcangdist.cpp tools/modcalcapcoord.cpp tools/modcalcaltaz.cpp tools/modcalcdaylength.cpp tools/modcalceclipticcoords.cpp tools/modcalcvizequinox.cpp tools/modcalcgalcoord.cpp tools/modcalcgeodcoord.cpp tools/modcalcjd.cpp tools/modcalcplanets.cpp tools/modcalcsidtime.cpp tools/modcalcvlsr.cpp tools/observinglist.cpp tools/obslistpopupmenu.cpp tools/sessionsortfilterproxymodel.cpp tools/obslistwizard.cpp tools/planetviewer.cpp tools/pvplotwidget.cpp tools/scriptargwidgets.cpp tools/scriptbuilder.cpp tools/scriptfunction.cpp tools/skycalendar.cpp tools/wutdialog.cpp tools/flagmanager.cpp tools/horizonmanager.cpp tools/nameresolver.cpp tools/polarishourangle.cpp #FIXME Port to KF5 #tools/moonphasetool.cpp tools/starhopper.cpp tools/eyepiecefield.cpp tools/exporteyepieceview.cpp tools/starhopperdialog.cpp tools/adddeepskyobject.cpp ) if(${KF5_VERSION} VERSION_EQUAL 5.18.0 OR ${KF5_VERSION} VERSION_GREATER 5.18.0) set(libkstarstools_SRCS ${libkstarstools_SRCS} tools/whatsinteresting/skyobjlistmodel.cpp tools/whatsinteresting/wiview.cpp tools/whatsinteresting/modelmanager.cpp tools/whatsinteresting/skyobjitem.cpp tools/whatsinteresting/wilpsettings.cpp tools/whatsinteresting/wiequipsettings.cpp tools/whatsinteresting/obsconditions.cpp tools/whatsinteresting/skyobjdescription.cpp ) endif() ki18n_wrap_ui(libkstarstools_ui_SRCS tools/altvstime.ui tools/argchangeviewoption.ui tools/argexportimage.ui tools/argloadcolorscheme.ui tools/arglooktoward.ui tools/argfindobject.ui tools/argprintimage.ui tools/argsetaltaz.ui tools/argsetcolor.ui tools/argsetgeolocation.ui tools/argsetlocaltime.ui tools/argsetradec.ui tools/argsettrack.ui tools/argtimescale.ui tools/argwaitfor.ui tools/argwaitforkey.ui tools/argzoom.ui tools/conjunctions.ui tools/eclipsetool.ui tools/modcalcangdist.ui tools/modcalcapcoord.ui tools/modcalcaltaz.ui tools/modcalcdaylength.ui tools/modcalceclipticcoords.ui tools/modcalcvizequinox.ui tools/modcalcgalcoord.ui tools/modcalcgeod.ui tools/modcalcjd.ui tools/modcalcplanets.ui tools/modcalcsidtime.ui tools/modcalcvlsr.ui tools/observinglist.ui tools/obslistwizard.ui tools/optionstreeview.ui tools/planetviewer.ui tools/scriptbuilder.ui tools/scriptnamedialog.ui tools/skycalendar.ui tools/wutdialog.ui tools/flagmanager.ui tools/starhopperdialog.ui tools/horizonmanager.ui tools/adddeepskyobject.ui tools/polarishourangle.ui ) if (${KF5_VERSION} VERSION_EQUAL 5.18.0 OR ${KF5_VERSION} VERSION_GREATER 5.18.0) ki18n_wrap_ui(libkstarstools_ui_SRCS tools/whatsinteresting/wilpsettings.ui tools/whatsinteresting/wiequipsettings.ui ) endif() set(libkstarswidgets_SRCS widgets/clicklabel.cpp widgets/dmsbox.cpp widgets/draglistbox.cpp widgets/fovwidget.cpp widgets/logedit.cpp widgets/magnitudespinbox.cpp widgets/mapcanvas.cpp widgets/thumbimage.cpp widgets/timespinbox.cpp widgets/timestepbox.cpp widgets/timeunitbox.cpp widgets/infoboxwidget.cpp # widgets/genericcalendarwidget.cpp # widgets/moonphasecalendarwidget.cpp widgets/kshelplabel.cpp widgets/unitspinboxwidget.cpp ) ki18n_wrap_ui(libkstarswidgets_ui_SRCS # widgets/genericcalendarwidget.ui widgets/unitspinboxwidget.ui ) set(kstars_KCFG_SRCS Options.kcfgc) set(kstars_options_SRCS options/opsadvanced.cpp options/opscatalog.cpp options/opscolors.cpp options/opsguides.cpp options/opssolarsystem.cpp options/opssatellites.cpp options/opssupernovae.cpp ) set(kstars_optionsui_SRCS options/opsadvanced.ui options/opscatalog.ui options/opscolors.ui options/opsguides.ui options/opssolarsystem.ui options/opssatellites.ui options/opssupernovae.ui ) set(kstars_dialogs_SRCS dialogs/addcatdialog.cpp dialogs/addlinkdialog.cpp dialogs/detaildialog.cpp dialogs/finddialog.cpp dialogs/focusdialog.cpp dialogs/fovdialog.cpp dialogs/locationdialog.cpp dialogs/timedialog.cpp dialogs/exportimagedialog.cpp ) set(kstars_dialogsui_SRCS dialogs/addcatdialog.ui dialogs/addlinkdialog.ui dialogs/details_database.ui dialogs/details_data.ui dialogs/details_data_comet.ui dialogs/details_links.ui dialogs/details_log.ui dialogs/details_position.ui dialogs/finddialog.ui dialogs/focusdialog.ui dialogs/fovdialog.ui dialogs/locationdialog.ui dialogs/wizwelcome.ui dialogs/wizlocation.ui dialogs/wizdownload.ui dialogs/wizdata.ui dialogs/newfov.ui dialogs/exportimagedialog.ui ) set(hips_SRCS hips/healpix.cpp hips/hipsrenderer.cpp hips/scanrender.cpp hips/pixcache.cpp hips/urlfiledownload.cpp hips/opships.cpp ) set(hips_manager_SRCS hips/hipsmanager.cpp ) set(oal_SRCS oal/log.cpp oal/observer.cpp oal/site.cpp oal/session.cpp oal/scope.cpp oal/eyepiece.cpp oal/filter.cpp oal/observation.cpp oal/lens.cpp oal/equipmentwriter.cpp oal/observeradd.cpp oal/execute.cpp ) set(printing_SRCS printing/detailstable.cpp printing/finderchart.cpp printing/foveditordialog.cpp printing/fovsnapshot.cpp printing/kstarsdocument.cpp printing/legend.cpp printing/loggingform.cpp printing/printingwizard.cpp printing/pwizchartconfig.cpp printing/pwizchartcontents.cpp printing/pwizfovbrowse.cpp printing/pwizfovconfig.cpp printing/pwizfovmanual.cpp printing/pwizfovsh.cpp printing/pwizfovtypeselection.cpp printing/pwizobjectselection.cpp printing/pwizprint.cpp printing/shfovexporter.cpp printing/simplefovexporter.cpp ) set(printingui_SRCS printing/foveditordialog.ui printing/pwizchartconfig.ui printing/pwizchartcontents.ui printing/pwizfovbrowse.ui printing/pwizfovconfig.ui printing/pwizfovmanual.ui printing/pwizfovsh.ui printing/pwizfovtypeselection.ui printing/pwizobjectselection.ui printing/pwizprint.ui printing/pwizwelcome.ui ) set( kstars_KCFG_SRCS Options.kcfgc ) set(libkstarscomponents_SRCS skycomponents/skylabeler.cpp skycomponents/highpmstarlist.cpp skycomponents/skymapcomposite.cpp skycomponents/skymesh.cpp skycomponents/linelistindex.cpp skycomponents/linelistlabel.cpp skycomponents/noprecessindex.cpp skycomponents/listcomponent.cpp skycomponents/pointlistcomponent.cpp skycomponents/solarsystemsinglecomponent.cpp skycomponents/solarsystemlistcomponent.cpp skycomponents/earthshadowcomponent.cpp skycomponents/asteroidscomponent.cpp skycomponents/cometscomponent.cpp skycomponents/planetmoonscomponent.cpp skycomponents/solarsystemcomposite.cpp skycomponents/satellitescomponent.cpp skycomponents/starcomponent.cpp skycomponents/deepstarcomponent.cpp skycomponents/deepskycomponent.cpp skycomponents/catalogcomponent.cpp skycomponents/syncedcatalogcomponent.cpp skycomponents/constellationartcomponent.cpp skycomponents/constellationboundarylines.cpp skycomponents/constellationlines.cpp skycomponents/constellationnamescomponent.cpp skycomponents/supernovaecomponent.cpp skycomponents/coordinategrid.cpp skycomponents/equatorialcoordinategrid.cpp skycomponents/horizontalcoordinategrid.cpp skycomponents/localmeridiancomponent.cpp skycomponents/ecliptic.cpp skycomponents/equator.cpp skycomponents/artificialhorizoncomponent.cpp skycomponents/hipscomponent.cpp skycomponents/horizoncomponent.cpp skycomponents/milkyway.cpp skycomponents/skycomponent.cpp skycomponents/skycomposite.cpp skycomponents/starblock.cpp skycomponents/starblocklist.cpp skycomponents/starblockfactory.cpp skycomponents/culturelist.cpp skycomponents/flagcomponent.cpp skycomponents/targetlistcomponent.cpp ) #LIST(APPEND libkstarscomponents_SRCS # #skycomponents/notifyupdatesui.cpp # ) IF (BUILD_KSTARS_LITE) set(libkstarstools_ui_klite_SRCS tools/nameresolver.cpp ) ENDIF () set(kstars_skyobjects_SRCS skyobjects/constellationsart.cpp skyobjects/deepskyobject.cpp skyobjects/jupitermoons.cpp skyobjects/planetmoons.cpp skyobjects/ksasteroid.cpp skyobjects/kscomet.cpp skyobjects/ksmoon.cpp skyobjects/ksearthshadow.cpp skyobjects/ksplanetbase.cpp skyobjects/ksplanet.cpp #skyobjects/kspluto.cpp skyobjects/kssun.cpp skyobjects/skyline.cpp skyobjects/skyobject.cpp skyobjects/skypoint.cpp skyobjects/starobject.cpp skyobjects/trailobject.cpp skyobjects/satellite.cpp skyobjects/satellitegroup.cpp skyobjects/supernova.cpp ) set(kstars_projection_SRCS projections/projector.cpp projections/lambertprojector.cpp projections/gnomonicprojector.cpp projections/stereographicprojector.cpp projections/orthographicprojector.cpp projections/azimuthalequidistantprojector.cpp projections/equirectangularprojector.cpp ) set(kstars_extra_SRCS auxiliary/colorscheme.cpp auxiliary/dms.cpp auxiliary/cachingdms.cpp auxiliary/geolocation.cpp auxiliary/ksfilereader.cpp auxiliary/ksuserdb.cpp auxiliary/binfilehelper.cpp auxiliary/ksutils.cpp auxiliary/ksdssimage.cpp auxiliary/ksdssdownloader.cpp auxiliary/nonlineardoublespinbox.cpp auxiliary/profileinfo.cpp auxiliary/filedownloader.cpp auxiliary/kspaths.cpp auxiliary/QRoundProgressBar.cpp auxiliary/skyobjectlistmodel.cpp auxiliary/ksnotification.cpp auxiliary/ksmessagebox.cpp auxiliary/QProgressIndicator.cpp auxiliary/ctkrangeslider.cpp time/simclock.cpp time/kstarsdatetime.cpp time/timezonerule.cpp ksnumbers.cpp kstarsdata.cpp texturemanager.cpp #to minimize number of indef KSTARS_LITE skypainter.cpp ) SET(kstars_extra_kstars_SRCS auxiliary/thememanager.cpp auxiliary/schememanager.cpp auxiliary/imageviewer.cpp auxiliary/xplanetimageviewer.cpp auxiliary/fov.cpp auxiliary/thumbnailpicker.cpp auxiliary/thumbnaileditor.cpp auxiliary/imageexporter.cpp auxiliary/kswizard.cpp auxiliary/qcustomplot.cpp kstarsdbus.cpp kspopupmenu.cpp ksalmanac.cpp kstarsactions.cpp kstarsinit.cpp kstars.cpp kstarssplash.cpp skymap.cpp skymapdrawabstract.cpp skymapqdraw.cpp skymapevents.cpp skyqpainter.cpp ) # Temporary solution to allow use of qml files from source dir DELETE SET(KSTARSLITE_CPP_OPTIONS -DSOURCE_DIR=\"${kstars_SOURCE_DIR}\" -DQML_IMPORT="${CMAKE_CURRENT_SOURCE_DIR}") set(klite_SRCS kstarslite.cpp kstarsliteinit.cpp skymaplite.cpp skymapliteevents.cpp #Wrappers kstarslite/skypointlite.cpp kstarslite/skyobjectlite.cpp #ImageProvider kstarslite/imageprovider.cpp #Dialogs kstarslite/dialogs/detaildialoglite.cpp kstarslite/dialogs/finddialoglite.cpp kstarslite/dialogs/locationdialoglite.cpp #RootNode kstarslite/skyitems/rootnode.cpp kstarslite/skyitems/skyopacitynode.cpp kstarslite/skyitems/typedeflite.h #SkyItems kstarslite/skyitems/skyitem.cpp kstarslite/skyitems/planetsitem.cpp kstarslite/skyitems/asteroidsitem.cpp kstarslite/skyitems/cometsitem.cpp kstarslite/skyitems/horizonitem.cpp kstarslite/skyitems/labelsitem.cpp kstarslite/skyitems/constellationnamesitem.cpp kstarslite/skyitems/staritem.cpp kstarslite/skyitems/deepstaritem.cpp kstarslite/skyitems/deepskyitem.cpp kstarslite/skyitems/constellationartitem.cpp kstarslite/skyitems/satellitesitem.cpp kstarslite/skyitems/supernovaeitem.cpp kstarslite/skyitems/fovitem.cpp kstarslite/skyitems/syncedcatalogitem.cpp #Line kstarslite/skyitems/lines/linesitem.cpp kstarslite/skyitems/lines/equatoritem.cpp kstarslite/skyitems/lines/eclipticitem.cpp kstarslite/skyitems/lines/milkywayitem.cpp #SkyNodes kstarslite/skyitems/skynodes/planetnode.cpp kstarslite/skyitems/skynodes/skynode.cpp kstarslite/skyitems/skynodes/pointsourcenode.cpp kstarslite/skyitems/skynodes/planetmoonsnode.cpp kstarslite/skyitems/skynodes/horizonnode.cpp kstarslite/skyitems/skynodes/labelnode.cpp kstarslite/skyitems/skynodes/guidelabelnode.cpp kstarslite/skyitems/skynodes/deepskynode.cpp kstarslite/skyitems/skynodes/dsosymbolnode.cpp kstarslite/skyitems/skynodes/skypolygonnode.cpp kstarslite/skyitems/skynodes/constellationartnode.cpp kstarslite/skyitems/skynodes/satellitenode.cpp kstarslite/skyitems/skynodes/supernovanode.cpp kstarslite/skyitems/skynodes/trixelnode.cpp kstarslite/skyitems/skynodes/fovsymbolnode.cpp #Nodes kstarslite/skyitems/skynodes/nodes/pointnode.cpp kstarslite/skyitems/skynodes/nodes/polynode.cpp kstarslite/skyitems/skynodes/nodes/linenode.cpp kstarslite/skyitems/skynodes/nodes/ellipsenode.cpp kstarslite/skyitems/skynodes/nodes/rectnode.cpp #Other kstarslite/deviceorientation.cpp ) set(kstarslite_libtess_SRC #libtess libtess/gluos.h libtess/priorityq-sort.h libtess/sweep.c libtess/tessmono.c libtess/dict-list.h libtess/glu.h libtess/tessellate.c libtess/dict.c libtess/geom.c libtess/memalloc.c libtess/mesh.c libtess/normal.c libtess/priorityq.c libtess/priorityq-heap.c libtess/render.c libtess/tess.c ) IF (BUILD_KSTARS_LITE) ADD_CUSTOM_TARGET(convert_translations ${CMAKE_SOURCE_DIR}/tools/convert_translations.sh ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) ADD_DEPENDENCIES(convert_translations fetch-translations) IF (ANDROID) ADD_CUSTOM_TARGET(convert_translations_to_android ${CMAKE_SOURCE_DIR}/tools/convert_translations.sh ${CMAKE_BINARY_DIR}/packaging/android/export/share/kstars WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) ADD_DEPENDENCIES(convert_translations_to_android fetch-translations) ENDIF () ENDIF () IF ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") SET_SOURCE_FILES_PROPERTIES(${kstarslite_libtess_SRC} PROPERTIES COMPILE_FLAGS "-Wno-error") ENDIF () #Qml files will be probably moved to user's data dir, but for use #with QtCreator it is more convenient to have them here set(kstarsliteqml_SRCS kstarslite/qml/main.qml kstarslite/qml/constants/Constants.qml kstarslite/qml/modules/SkyMapLiteWrapper.qml kstarslite/qml/modules/BottomMenu.qml kstarslite/qml/modules/KSPage.qml kstarslite/qml/modules/KSListView.qml kstarslite/qml/modules/KSLabel.qml kstarslite/qml/modules/KSText.qml kstarslite/qml/modules/KSTabButton.qml kstarslite/qml/modules/KSTab.qml kstarslite/qml/modules/KSTabBarArrow.qml kstarslite/qml/modules/KSTextField.qml kstarslite/qml/modules/KSButton.qml kstarslite/qml/modules/TopMenu.qml kstarslite/qml/modules/helpers/TopMenuButton.qml kstarslite/qml/modules/helpers/BottomMenuButton.qml kstarslite/qml/modules/Splash.qml kstarslite/qml/modules/helpers/TimeSpinBox.qml kstarslite/qml/modules/TimePage.qml #Popups kstarslite/qml/modules/popups/ProjectionsPopup.qml kstarslite/qml/modules/popups/FOVPopup.qml kstarslite/qml/modules/popups/ColorSchemePopup.qml #Menus kstarslite/qml/modules/menus/ContextMenu.qml #Helpers kstarslite/qml/modules/helpers/PassiveNotification.qml kstarslite/qml/modules/helpers/KSMenuItem.qml kstarslite/qml/modules/helpers/TelescopeControl.qml #Dialogs kstarslite/qml/dialogs/FindDialog.qml kstarslite/qml/dialogs/LocationDialog.qml kstarslite/qml/dialogs/DetailsDialog.qml kstarslite/qml/dialogs/AboutDialog.qml kstarslite/qml/dialogs/helpers/DetailsItem.qml kstarslite/qml/dialogs/helpers/DetailsAddLink.qml kstarslite/qml/dialogs/helpers/LocationEdit.qml kstarslite/qml/dialogs/helpers/LocationLoading.qml kstarslite/qml/dialogs/menus/DetailsLinkMenu.qml kstarslite/qml/dialogs/menus/LocationsGeoMenu.qml #INDI kstarslite/qml/indi/INDIControlPanel.qml kstarslite/qml/indi/DevicePanel.qml kstarslite/qml/indi/ImagePreview.qml kstarslite/qml/indi/modules/MotionControl.qml kstarslite/qml/indi/modules/Led.qml kstarslite/qml/indi/modules/KSLed.qml kstarslite/qml/indi/modules/Property.qml kstarslite/qml/indi/modules/KSComboBox.qml kstarslite/qml/indi/modules/KSButtonSwitch.qml kstarslite/qml/indi/modules/KSCheckBox.qml kstarslite/qml/indi/modules/KSINDIText.qml kstarslite/qml/indi/modules/KSINDITextField.qml kstarslite/qml/indi/modules/KSButtonsSwitchRow.qml #Tutorial kstarslite/qml/modules/tutorial/TutorialPopup.qml kstarslite/qml/modules/tutorial/TutorialExitPopup.qml kstarslite/qml/modules/tutorial/TutorialStep1.qml kstarslite/qml/modules/tutorial/TutorialStep2.qml kstarslite/qml/modules/tutorial/TutorialStep3.qml kstarslite/qml/modules/tutorial/TutorialStep4.qml kstarslite/qml/modules/tutorial/TutorialStep5.qml kstarslite/qml/modules/tutorial/TutorialPane.qml ) add_subdirectory(kstarslite/qml) ADD_CUSTOM_TARGET(kstarsliteqml SOURCES ${kstarsliteqml_SRCS}) if(ANDROID) add_subdirectory(kstarslite/res) endif(ANDROID) set(kstars_SRCS ${indi_SRCS} ${fits_SRCS} ${ekos_SRCS} ${libkstarswidgets_SRCS} ${libkstarscomponents_SRCS} ${libkstarstools_SRCS} ${kstars_extra_SRCS} ${kstars_extra_kstars_SRCS} ${kstars_projection_SRCS} ${xplanet_SRCS} ${kstars_options_SRCS} ${kstars_skyobjects_SRCS} ${kstars_dialogs_SRCS} ${hips_SRCS} ${oal_SRCS} ${printing_SRCS} #KStars Lite ${kstarslite_SRCS} # Generated files ${libkstarstools_ui_SRCS} ${libkstarswidgets_ui_SRCS} ) set(kstarslite_SRCS ${indi_klite_SRCS} ${libkstarscomponents_SRCS} ${kstars_extra_SRCS} ${kstars_projection_SRCS} ${kstars_skyobjects_SRCS} # KStars Lite sources ${klite_SRCS} # Generated files ${libkstarstools_ui_klite_SRCS} ) # Generate all the necessary QLoggingCategory files ecm_qt_declare_logging_category(kstars_SRCS HEADER kstars_debug.h IDENTIFIER KSTARS CATEGORY_NAME org.kde.kstars) ecm_qt_declare_logging_category(kstars_SRCS HEADER indi_debug.h IDENTIFIER KSTARS_INDI CATEGORY_NAME org.kde.kstars.indi) ecm_qt_declare_logging_category(kstars_SRCS HEADER fits_debug.h IDENTIFIER KSTARS_FITS CATEGORY_NAME org.kde.kstars.fits) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_debug.h IDENTIFIER KSTARS_EKOS CATEGORY_NAME org.kde.kstars.ekos) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_capture_debug.h IDENTIFIER KSTARS_EKOS_CAPTURE CATEGORY_NAME org.kde.kstars.ekos.capture) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_focus_debug.h IDENTIFIER KSTARS_EKOS_FOCUS CATEGORY_NAME org.kde.kstars.ekos.focus) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_align_debug.h IDENTIFIER KSTARS_EKOS_ALIGN CATEGORY_NAME org.kde.kstars.ekos.align) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_guide_debug.h IDENTIFIER KSTARS_EKOS_GUIDE CATEGORY_NAME org.kde.kstars.ekos.guide) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_mount_debug.h IDENTIFIER KSTARS_EKOS_MOUNT CATEGORY_NAME org.kde.kstars.ekos.mount) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_scheduler_debug.h IDENTIFIER KSTARS_EKOS_SCHEDULER CATEGORY_NAME org.kde.kstars.ekos.scheduler) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_observatory_debug.h IDENTIFIER KSTARS_EKOS_OBSERVATORY CATEGORY_NAME org.kde.kstars.ekos.observatory) kconfig_add_kcfg_files(kstars_SRCS ${kstars_KCFG_SRCS}) ecm_qt_declare_logging_category(kstarslite_SRCS HEADER kstars_debug.h IDENTIFIER KSTARS CATEGORY_NAME org.kde.kstars) ecm_qt_declare_logging_category(kstarslite_SRCS HEADER fits_debug.h IDENTIFIER KSTARS_FITS CATEGORY_NAME org.kde.kstars.fits) kconfig_add_kcfg_files(kstarslite_SRCS ${kstars_KCFG_SRCS}) IF (UNITY_BUILD) ENABLE_UNITY_BUILD(kstars kstars_SRCS 10 cpp) ENABLE_UNITY_BUILD(kstarslite kstarslite_SRCS 10 cpp) ENDIF () set(kstars_SRCS ${kstars_SRCS} ${fits2_SRCS} ${sep_SRCS} ${hough_SRCS} ${hips_manager_SRCS}) set(kstarslite_SRCS ${kstarslite_SRCS} ${fits_klite_SRCS} ${sep_SRCS} ${hough_SRCS} ${fits2_klite_SRCS} ${kstarslite_libtess_SRC}) IF (NOT ANDROID) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.xml kstars.h KStars) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.SimClock.xml simclock.h SimClock) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.FOV.xml fov.h FOV) IF (INDI_FOUND) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.INDI.xml indi/indidbus.h INDIDBus) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.xml ekos/manager.h Ekos::Manager) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Capture.xml ekos/capture/capture.h Ekos::Capture) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Focus.xml ekos/focus/focus.h Ekos::Focus) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Guide.xml ekos/guide/guide.h Ekos::Guide) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Align.xml ekos/align/align.h Ekos::Align) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Mount.xml ekos/mount/mount.h Ekos::Mount) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Dome.xml ekos/auxiliary/dome.h Ekos::Dome) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Weather.xml ekos/auxiliary/weather.h Ekos::Weather) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.DustCap.xml ekos/auxiliary/dustcap.h Ekos::DustCap) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Scheduler.xml ekos/scheduler/scheduler.h Ekos::Scheduler) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Observatory.xml ekos/observatory/observatory.h Ekos::Observatory) ENDIF () ki18n_wrap_ui(kstars_SRCS ${indiui_SRCS} ${ui_SRCS} ${fitsui_SRCS} ${ekosui_SRCS} ${xplanetui_SRCS} ${kstars_optionsui_SRCS} ${kstars_dialogsui_SRCS} ${printingui_SRCS} auxiliary/thumbnailpicker.ui auxiliary/thumbnaileditor.ui oal/observeradd.ui oal/equipmentwriter.ui oal/execute.ui hips/opships.ui hips/opshipsdisplay.ui hips/opshipscache.ui #skycomponents/notifyupdatesui.ui ) add_library(KStarsLib STATIC ${kstars_SRCS}) include(GenerateExportHeader) generate_export_header(KStarsLib) target_link_libraries(KStarsLib LibKSDataHandlers htmesh KF5::Crash KF5::I18n KF5::NewStuff KF5::KIOFileWidgets KF5::WidgetsAddons KF5::Plotting KF5::Notifications Qt5::Gui Qt5::PrintSupport Qt5::Sql Qt5::Svg Qt5::Qml Qt5::Quick Qt5::Network #Qt5::Positioning Qt5::Concurrent Qt5::WebSockets ${ZLIB_LIBRARIES} ) if (Qt5Keychain_FOUND) target_include_directories(KStarsLib PUBLIC ${QTKEYCHAIN_INCLUDE_DIRS}) target_link_libraries(KStarsLib ${QTKEYCHAIN_LIBRARIES}) endif(Qt5Keychain_FOUND) if (Qt5DataVisualization_FOUND) target_link_libraries(KStarsLib Qt5::DataVisualization) endif(Qt5DataVisualization_FOUND) if (KF5NotifyConfig_FOUND) target_link_libraries(KStarsLib KF5::NotifyConfig) endif(KF5NotifyConfig_FOUND) if(NOT WIN32) target_link_libraries(KStarsLib m) endif(NOT WIN32) ENDIF () if (BUILD_KSTARS_LITE) add_library(KStarsLiteLib STATIC ${kstarslite_SRCS}) target_link_libraries(KStarsLiteLib LibKSDataHandlers htmesh KF5::I18n KF5::Plotting KF5::ConfigGui Qt5::Gui Qt5::Sql Qt5::Qml Qt5::Quick Qt5::QuickControls2 Qt5::Positioning Qt5::PositioningQuick Qt5::Concurrent ${ZLIB_LIBRARIES} ) if (ANDROID) target_link_libraries(KStarsLiteLib Qt5::AndroidExtras) endif () endif () if (CFITSIO_FOUND) if (NOT ANDROID) target_include_directories(KStarsLib PUBLIC ${CFITSIO_INCLUDE_DIR}) target_link_libraries(KStarsLib ${CFITSIO_LIBRARIES}) endif() if (BUILD_KSTARS_LITE) target_include_directories(KStarsLiteLib PUBLIC ${CFITSIO_INCLUDE_DIR}) target_link_libraries(KStarsLiteLib ${CFITSIO_LIBRARIES}) endif() endif(CFITSIO_FOUND) if(INDI_FOUND) if (NOT ANDROID) find_package(Nova REQUIRED) include_directories(${NOVA_INCLUDE_DIR}) endif () ## Support Multiple Platforms. All Require INDI ## WIN32 Desktop: Requires INDI Qt5 Client + GSL ## WIN32 Lite: Requires INDI Qt5 Client ## Linux + MacOS Desktop: Requires INDI Client + GSL ## Linux + MacOS Lite: Requires INDI Qt5 Client ## Android: Requires INDI Qt5 Client built for Android if (NOT ANDROID) find_package(GSL REQUIRED) include_directories(${GSL_INCLUDE_DIRS}) target_link_libraries(KStarsLib ${GSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} KF5::Notifications) endif () if(WIN32 OR ANDROID) if(ANDROID) target_link_libraries(KStarsLiteLib ${INDI_CLIENT_ANDROID_LIBRARIES} ${CFITSIO_LIBRARIES} ${LIBRAW_LIBRARIES}) target_compile_options(KStarsLiteLib PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) else(ANDROID) target_link_libraries(KStarsLib ${INDI_CLIENT_LIBRARIES} ${NOVA_LIBRARIES}) endif(ANDROID) else(WIN32 OR ANDROID) if (BUILD_KSTARS_LITE) target_link_libraries(KStarsLiteLib ${INDI_CLIENT_QT_LIBRARIES} ${NOVA_LIBRARIES} z) target_compile_options(KStarsLiteLib PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) endif(BUILD_KSTARS_LITE) target_link_libraries(KStarsLib ${INDI_CLIENT_LIBRARIES} ${NOVA_LIBRARIES} z) endif(WIN32 OR ANDROID) endif(INDI_FOUND) if(WCSLIB_FOUND) target_link_libraries(KStarsLib ${WCSLIB_LIBRARIES}) if (BUILD_KSTARS_LITE) target_link_libraries(KStarsLiteLib ${WCSLIB_LIBRARIES}) endif() endif (WCSLIB_FOUND) if(LibRaw_FOUND) if (NOT ANDROID) target_link_libraries(KStarsLib ${LibRaw_LIBRARIES}) endif() if (BUILD_KSTARS_LITE) target_link_libraries(KStarsLiteLib ${LibRaw_LIBRARIES}) endif() endif (LibRaw_FOUND) # Link libnova if found if (NOVA_FOUND) target_link_libraries(KStarsLib ${NOVA_LIBRARIES}) endif() #FIXME Enable OpenGL Later #if( OPENGL_FOUND ) # target_link_libraries(KStarsLib # ${OPENGL_LIBRARIES} # ${QT_QTOPENGL_LIBRARY} # ) #endif( OPENGL_FOUND ) set (KSTARS_APP_SRCS main.cpp ) # add icon to application sources ecm_add_app_icon(KSTARS_APP_SRCS ICONS ${CMAKE_CURRENT_SOURCE_DIR}/icons/16-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/32-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/48-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/64-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/128-apps-kstars.png ) qt5_add_resources(KSTARS_APP_SRCS data/kstars.qrc) if (ANDROID) add_library(kstars SHARED ${KSTARS_APP_SRCS}) target_compile_options(kstars PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) add_dependencies(KStarsLiteLib cfitsio indi raw) target_link_libraries(kstars KStarsLiteLib) else () if (BUILD_KSTARS_LITE) add_executable(kstars_lite ${KSTARS_APP_SRCS}) target_compile_options(kstars_lite PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) target_link_libraries(kstars_lite KStarsLiteLib) endif() add_executable(kstars ${KSTARS_APP_SRCS}) target_link_libraries(kstars KStarsLib) endif () install(TARGETS kstars ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.kstars.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES kstars.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(FILES kstars.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) if(INDI_FOUND) #install(FILES ekos/mount/mountbox.qml DESTINATION ${KDE_INSTALL_DATADIR}/kstars/ekos/mount/qml) #install(DIRECTORY ekos/mount/ DESTINATION ${KDE_INSTALL_DATADIR}/kstars/ekos/mount/qml # FILES_MATCHING PATTERN "*.png") endif() if (NOT ANDROID AND BUILD_KSTARS_LITE) install(TARGETS kstars_lite ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) endif() diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp index 6dabeb04d..6a72873fb 100644 --- a/kstars/ekos/guide/guide.cpp +++ b/kstars/ekos/guide/guide.cpp @@ -1,3762 +1,3767 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "guide.h" #include "guideadaptor.h" #include "kstars.h" #include "ksmessagebox.h" #include "ksnotification.h" #include "kstarsdata.h" #include "opscalibration.h" #include "opsguide.h" #include "Options.h" #include "auxiliary/QProgressIndicator.h" #include "ekos/auxiliary/darklibrary.h" #include "externalguide/linguider.h" #include "externalguide/phd2.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsviewer.h" #include "internalguide/internalguider.h" #include #include #include #include "ui_manualdither.h" #define CAPTURE_TIMEOUT_THRESHOLD 30000 namespace Ekos { Guide::Guide() : QWidget() { // #1 Setup UI setupUi(this); // #2 Register DBus qRegisterMetaType("Ekos::GuideState"); qDBusRegisterMetaType(); new GuideAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Guide", this); // #3 Init Plots initPlots(); // #4 Init View initView(); // #5 Load all settings loadSettings(); // #6 Init Connections initConnections(); // Image Filters for (auto &filter : FITSViewer::filterTypes) filterCombo->addItem(filter); // Progress Indicator pi = new QProgressIndicator(this); controlLayout->addWidget(pi, 1, 2, 1, 1); showFITSViewerB->setIcon( QIcon::fromTheme("kstars_fitsviewer")); connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Guide::showFITSViewer); showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideAutoScaleGraphB->setIcon( QIcon::fromTheme("zoom-fit-best")); connect(guideAutoScaleGraphB, &QPushButton::clicked, this, &Ekos::Guide::slotAutoScaleGraphs); guideAutoScaleGraphB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideSaveDataB->setIcon( QIcon::fromTheme("document-save")); connect(guideSaveDataB, &QPushButton::clicked, this, &Ekos::Guide::exportGuideData); guideSaveDataB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideDataClearB->setIcon( QIcon::fromTheme("application-exit")); connect(guideDataClearB, &QPushButton::clicked, this, &Ekos::Guide::clearGuideGraphs); guideDataClearB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Exposure //Should we set the range for the spin box here? QList exposureValues; exposureValues << 0.02 << 0.05 << 0.1 << 0.2 << 0.5 << 1 << 1.5 << 2 << 2.5 << 3 << 3.5 << 4 << 4.5 << 5 << 6 << 7 << 8 << 9 << 10 << 15 << 30; exposureIN->setRecommendedValues(exposureValues); connect(exposureIN, &NonLinearDoubleSpinBox::editingFinished, this, &Ekos::Guide::saveDefaultGuideExposure); // Init Internal Guider always internalGuider = new InternalGuider(); KConfigDialog *dialog = new KConfigDialog(this, "guidesettings", Options::self()); opsCalibration = new OpsCalibration(internalGuider); KPageWidgetItem *page = dialog->addPage(opsCalibration, i18n("Calibration")); page->setIcon(QIcon::fromTheme("tool-measure")); opsGuide = new OpsGuide(); connect(opsGuide, &OpsGuide::settingsUpdated, [this]() { onThresholdChanged(Options::guideAlgorithm()); configurePHD2Camera(); }); page = dialog->addPage(opsGuide, i18n("Guide")); page->setIcon(QIcon::fromTheme("kstars_guides")); internalGuider->setGuideView(guideView); // Set current guide type setGuiderType(-1); //This allows the current guideSubframe option to be loaded. if(guiderType == GUIDE_PHD2) setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); } Guide::~Guide() { delete guider; } void Guide::handleHorizontalPlotSizeChange() { driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); driftPlot->replot(); calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0); calibrationPlot->replot(); } void Guide::handleVerticalPlotSizeChange() { driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); driftPlot->replot(); calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0); calibrationPlot->replot(); } void Guide::guideAfterMeridianFlip() { //This will clear the tracking box selection //The selected guide star is no longer valid due to the flip guideView->setTrackingBoxEnabled(false); starCenter = QVector3D(); if (Options::resetGuideCalibration()) clearCalibration(); guide(); } void Guide::resizeEvent(QResizeEvent *event) { if (event->oldSize().width() != -1) { if (event->oldSize().width() != size().width()) handleHorizontalPlotSizeChange(); else if (event->oldSize().height() != size().height()) handleVerticalPlotSizeChange(); } else { QTimer::singleShot(10, this, &Ekos::Guide::handleHorizontalPlotSizeChange); } } void Guide::buildTarget() { double accuracyRadius = accuracyRadiusSpin->value(); Options::setGuiderAccuracyThreshold(accuracyRadius); if (centralTarget) { concentricRings->data()->clear(); redTarget->data()->clear(); yellowTarget->data()->clear(); centralTarget->data()->clear(); } else { concentricRings = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); redTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); yellowTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); centralTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); } const int pointCount = 200; QVector circleRings( pointCount * (5)); //Have to multiply by the number of rings, Rings at : 25%, 50%, 75%, 125%, 175% QVector circleCentral(pointCount); QVector circleYellow(pointCount); QVector circleRed(pointCount); int circleRingPt = 0; for (int i = 0; i < pointCount; i++) { double theta = i / static_cast(pointCount) * 2 * M_PI; for (double ring = 1; ring < 8; ring++) { if (ring != 4 && ring != 6) { if (i % (9 - static_cast(ring)) == 0) //This causes fewer points to draw on the inner circles. { circleRings[circleRingPt] = QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta), accuracyRadius * ring * 0.25 * qSin(theta)); circleRingPt++; } } } circleCentral[i] = QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta)); circleYellow[i] = QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta)); circleRed[i] = QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta)); } concentricRings->setLineStyle(QCPCurve::lsNone); concentricRings->setScatterSkip(0); concentricRings->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QColor(255, 255, 255, 150), 1)); concentricRings->data()->set(circleRings, true); redTarget->data()->set(circleRed, true); yellowTarget->data()->set(circleYellow, true); centralTarget->data()->set(circleCentral, true); concentricRings->setPen(QPen(Qt::white)); redTarget->setPen(QPen(Qt::red)); yellowTarget->setPen(QPen(Qt::yellow)); centralTarget->setPen(QPen(Qt::green)); concentricRings->setBrush(Qt::NoBrush); redTarget->setBrush(QBrush(QColor(255, 0, 0, 50))); yellowTarget->setBrush( QBrush(QColor(0, 255, 0, 50))); //Note this is actually yellow. It is green on top of red with equal opacity. centralTarget->setBrush(QBrush(QColor(0, 255, 0, 50))); if (driftPlot->size().width() > 0) driftPlot->replot(); } void Guide::clearGuideGraphs() { driftGraph->graph(0)->data()->clear(); //RA data driftGraph->graph(1)->data()->clear(); //DEC data driftGraph->graph(2)->data()->clear(); //RA highlighted point driftGraph->graph(3)->data()->clear(); //DEC highlighted point driftGraph->graph(4)->data()->clear(); //RA Pulses driftGraph->graph(5)->data()->clear(); //DEC Pulses driftPlot->graph(0)->data()->clear(); //Guide data driftPlot->graph(1)->data()->clear(); //Guide highlighted point driftGraph->clearItems(); //Clears dither text items from the graph driftGraph->replot(); driftPlot->replot(); //Since the labels got cleared with clearItems above. setupNSEWLabels(); } void Guide::clearCalibrationGraphs() { calibrationPlot->graph(0)->data()->clear(); //RA out calibrationPlot->graph(1)->data()->clear(); //RA back calibrationPlot->graph(2)->data()->clear(); //DEC out calibrationPlot->graph(3)->data()->clear(); //DEC back calibrationPlot->replot(); } void Guide::setupNSEWLabels() { //Labels for N/S/E/W QColor raLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); QColor deLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); //DriftGraph { QCPItemText *northLabel = new QCPItemText(driftGraph); northLabel->setColor(deLabelColor); northLabel->setText(i18nc("North", "N")); northLabel->position->setType(QCPItemPosition::ptViewportRatio); northLabel->position->setCoords(0.6, 0.1); northLabel->setVisible(true); QCPItemText *southLabel = new QCPItemText(driftGraph); southLabel->setColor(deLabelColor); southLabel->setText(i18nc("South", "S")); southLabel->position->setType(QCPItemPosition::ptViewportRatio); southLabel->position->setCoords(0.6, 0.8); southLabel->setVisible(true); QCPItemText *westLabel = new QCPItemText(driftGraph); westLabel->setColor(raLabelColor); westLabel->setText(i18nc("West", "W")); westLabel->position->setType(QCPItemPosition::ptViewportRatio); westLabel->position->setCoords(0.8, 0.1); westLabel->setVisible(true); QCPItemText *eastLabel = new QCPItemText(driftGraph); eastLabel->setColor(raLabelColor); eastLabel->setText(i18nc("East", "E")); eastLabel->position->setType(QCPItemPosition::ptViewportRatio); eastLabel->position->setCoords(0.8, 0.8); eastLabel->setVisible(true); } //DriftPlot { QCPItemText *northLabel = new QCPItemText(driftPlot); northLabel->setColor(deLabelColor); northLabel->setText(i18nc("North", "N")); northLabel->position->setType(QCPItemPosition::ptViewportRatio); northLabel->position->setCoords(0.25, 0.2); northLabel->setVisible(true); QCPItemText *southLabel = new QCPItemText(driftPlot); southLabel->setColor(deLabelColor); southLabel->setText(i18nc("South", "S")); southLabel->position->setType(QCPItemPosition::ptViewportRatio); southLabel->position->setCoords(0.25, 0.7); southLabel->setVisible(true); QCPItemText *westLabel = new QCPItemText(driftPlot); westLabel->setColor(raLabelColor); westLabel->setText(i18nc("West", "W")); westLabel->position->setType(QCPItemPosition::ptViewportRatio); westLabel->position->setCoords(0.8, 0.75); westLabel->setVisible(true); QCPItemText *eastLabel = new QCPItemText(driftPlot); eastLabel->setColor(raLabelColor); eastLabel->setText(i18nc("East", "E")); eastLabel->position->setType(QCPItemPosition::ptViewportRatio); eastLabel->position->setCoords(0.3, 0.75); eastLabel->setVisible(true); } } void Guide::slotAutoScaleGraphs() { double accuracyRadius = accuracyRadiusSpin->value(); double key = guideTimer.elapsed() / 1000.0; driftGraph->xAxis->setRange(key - 60, key); driftGraph->yAxis->setRange(-3, 3); driftGraph->graph(0)->rescaleValueAxis(true); driftGraph->replot(); driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->graph(0)->rescaleAxes(true); driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); driftPlot->replot(); calibrationPlot->xAxis->setRange(-10, 10); calibrationPlot->yAxis->setRange(-10, 10); calibrationPlot->graph(0)->rescaleAxes(true); calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0); calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0); calibrationPlot->replot(); } void Guide::guideHistory() { int sliderValue = guideSlider->value(); latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); driftGraph->graph(2)->data()->clear(); //Clear RA highlighted point driftGraph->graph(3)->data()->clear(); //Clear DEC highlighted point driftPlot->graph(1)->data()->clear(); //Clear Guide highlighted point double t = driftGraph->graph(0)->dataMainKey(sliderValue); //Get time from RA data double ra = driftGraph->graph(0)->dataMainValue(sliderValue); //Get RA from RA data double de = driftGraph->graph(1)->dataMainValue(sliderValue); //Get DEC from DEC data double raPulse = driftGraph->graph(4)->dataMainValue(sliderValue); //Get RA Pulse from RA pulse data double dePulse = driftGraph->graph(5)->dataMainValue(sliderValue); //Get DEC Pulse from DEC pulse data driftGraph->graph(2)->addData(t, ra); //Set RA highlighted point driftGraph->graph(3)->addData(t, de); //Set DEC highlighted point //This will allow the graph to scroll left and right along with the guide slider if (driftGraph->xAxis->range().contains(t) == false) { if(t < driftGraph->xAxis->range().lower) { driftGraph->xAxis->setRange(t, t + driftGraph->xAxis->range().size()); } if(t > driftGraph->xAxis->range().upper) { driftGraph->xAxis->setRange(t - driftGraph->xAxis->range().size(), t); } } driftGraph->replot(); driftPlot->graph(1)->addData(ra, de); //Set guide highlighted point driftPlot->replot(); if(!graphOnLatestPt) { QTime localTime = guideTimer; localTime = localTime.addSecs(t); QPoint localTooltipCoordinates = driftGraph->graph(0)->dataPixelPosition(sliderValue).toPoint(); QPoint globalTooltipCoordinates = driftGraph->mapToGlobal(localTooltipCoordinates); if(raPulse == 0 && dePulse == 0) { QToolTip::showText( globalTooltipCoordinates, i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds", "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
", localTime.toString("hh:mm:ss AP"), QString::number(ra, 'f', 2), QString::number(de, 'f', 2))); } else { QToolTip::showText( globalTooltipCoordinates, i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is RA Pulse in ms; %5 is DE Pulse in ms", "" "" "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
RA Pulse: %4 ms
DE Pulse: %5 ms
", localTime.toString("hh:mm:ss AP"), QString::number(ra, 'f', 2), QString::number(de, 'f', 2), QString::number(raPulse, 'f', 2), QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph. } } } void Guide::setLatestGuidePoint(bool isChecked) { graphOnLatestPt = isChecked; if(isChecked) guideSlider->setValue(guideSlider->maximum()); } void Guide::toggleShowRAPlot(bool isChecked) { Options::setRADisplayedOnGuideGraph(isChecked); driftGraph->graph(0)->setVisible(isChecked); driftGraph->graph(2)->setVisible(isChecked); driftGraph->replot(); } void Guide::toggleShowDEPlot(bool isChecked) { Options::setDEDisplayedOnGuideGraph(isChecked); driftGraph->graph(1)->setVisible(isChecked); driftGraph->graph(3)->setVisible(isChecked); driftGraph->replot(); } void Guide::toggleRACorrectionsPlot(bool isChecked) { Options::setRACorrDisplayedOnGuideGraph(isChecked); driftGraph->graph(4)->setVisible(isChecked); updateCorrectionsScaleVisibility(); } void Guide::toggleDECorrectionsPlot(bool isChecked) { Options::setDECorrDisplayedOnGuideGraph(isChecked); driftGraph->graph(5)->setVisible(isChecked); updateCorrectionsScaleVisibility(); } void Guide::updateCorrectionsScaleVisibility() { bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph()); driftGraph->yAxis2->setVisible(isVisible); correctionSlider->setVisible(isVisible); driftGraph->replot(); } void Guide::setCorrectionGraphScale() { driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower * correctionSlider->value(), driftGraph->yAxis->range().upper * correctionSlider->value()); driftGraph->replot(); } void Guide::exportGuideData() { int numPoints = driftGraph->graph(0)->dataCount(); if (numPoints == 0) return; QUrl exportFile = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Export Guide Data"), guideURLPath, "CSV File (*.csv)"); if (exportFile.isEmpty()) // if user presses cancel return; if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false) exportFile.setPath(exportFile.toLocalFile() + ".csv"); QString path = exportFile.toLocalFile(); if (QFile::exists(path)) { int r = KMessageBox::warningContinueCancel(nullptr, i18n("A file named \"%1\" already exists. " "Overwrite it?", exportFile.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } if (!exportFile.isValid()) { QString message = i18n("Invalid URL: %1", exportFile.url()); KSNotification::sorry(message, i18n("Invalid URL")); return; } QFile file; file.setFileName(path); if (!file.open(QIODevice::WriteOnly)) { QString message = i18n("Unable to write to file %1", path); KSNotification::sorry(message, i18n("Could Not Open File")); return; } QTextStream outstream(&file); outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; for (int i = 0; i < numPoints; i++) { double t = driftGraph->graph(0)->dataMainKey(i); double ra = driftGraph->graph(0)->dataMainValue(i); double de = driftGraph->graph(1)->dataMainValue(i); double raPulse = driftGraph->graph(4)->dataMainValue(i); double dePulse = driftGraph->graph(5)->dataMainValue(i); QTime localTime = guideTimer; localTime = localTime.addSecs(t); outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' << dePulse << ',' << endl; } appendLogText(i18n("Guide Data Saved as: %1", path)); file.close(); } QString Guide::setRecommendedExposureValues(QList values) { exposureIN->setRecommendedValues(values); return exposureIN->getRecommendedValuesString(); } void Guide::addCamera(ISD::GDInterface *newCCD) { ISD::CCD *ccd = static_cast(newCCD); if (CCDs.contains(ccd)) return; if(guiderType != GUIDE_INTERNAL) { connect(ccd, &ISD::CCD::newBLOBManager, [ccd, this](INDI::Property * prop) { if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) { ccd->setBLOBEnabled(false); //This will disable PHD2 external guide frames until it is properly connected. currentCCD = ccd; } }); guiderCombo->clear(); guiderCombo->setEnabled(false); if (guiderType == GUIDE_PHD2) guiderCombo->addItem("PHD2"); else guiderCombo->addItem("LinGuider"); } else { guiderCombo->setEnabled(true); guiderCombo->addItem(ccd->getDeviceName()); } CCDs.append(ccd); checkCCD(); configurePHD2Camera(); } void Guide::configurePHD2Camera() { //Maybe something like this can be done for Linguider? //But for now, Linguider doesn't support INDI Cameras if(guiderType != GUIDE_PHD2) return; //This prevents a crash if phd2guider is null if(!phd2Guider) return; //This way it doesn't check if the equipment isn't connected yet. //It will check again when the equipment is connected. if(!phd2Guider->isConnected()) return; //This way it doesn't check if the equipment List has not been received yet. //It will ask for the list. When the list is received it will check again. if(phd2Guider->getCurrentCamera().isEmpty()) { phd2Guider->requestCurrentEquipmentUpdate(); return; } //this checks to see if a CCD in the list matches the name of PHD2's camera ISD::CCD *ccdMatch = nullptr; QString currentPHD2CameraName = "None"; foreach(ISD::CCD *ccd, CCDs) { if(phd2Guider->getCurrentCamera().contains(ccd->getDeviceName())) { ccdMatch = ccd; currentPHD2CameraName = (phd2Guider->getCurrentCamera()); break; } } //If this method gives the same result as last time, no need to update the Camera info again. //That way the user doesn't see a ton of messages printing about the PHD2 external camera. //But lets make sure the blob is set correctly every time. if(lastPHD2CameraName == currentPHD2CameraName) { setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); return; } //This means that a Guide Camera was connected before but it changed. if(currentCCD) setExternalGuiderBLOBEnabled(false); //Updating the currentCCD currentCCD = ccdMatch; //This updates the last camera name for the next time it is checked. lastPHD2CameraName = currentPHD2CameraName; //This sets a boolean that allows you to tell if the PHD2 camera is in Ekos phd2Guider->setCurrentCameraIsNotInEkos(currentCCD == nullptr); if(phd2Guider->isCurrentCameraNotInEkos()) { appendLogText( i18n("PHD2's current camera: %1, is NOT connected to Ekos. The PHD2 Guide Star Image will be received, but the full external guide frames cannot.", phd2Guider->getCurrentCamera())); subFrameCheck->setEnabled(false); //We don't want to actually change the user's subFrame Setting for when a camera really is connected, just check the box to tell the user. disconnect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); subFrameCheck->setChecked(true); return; } appendLogText( i18n("PHD2's current camera: %1, IS connected to Ekos. You can select whether to use the full external guide frames or just receive the PHD2 Guide Star Image using the SubFrame checkbox.", phd2Guider->getCurrentCamera())); subFrameCheck->setEnabled(true); connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); subFrameCheck->setChecked(Options::guideSubframeEnabled()); } void Guide::addGuideHead(ISD::GDInterface *newCCD) { if (guiderType != GUIDE_INTERNAL) return; ISD::CCD *ccd = static_cast(newCCD); CCDs.append(ccd); QString guiderName = ccd->getDeviceName() + QString(" Guider"); if (guiderCombo->findText(guiderName) == -1) { guiderCombo->addItem(guiderName); //CCDs.append(static_cast (newCCD)); } //checkCCD(CCDs.count()-1); //guiderCombo->setCurrentIndex(CCDs.count()-1); //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2); } void Guide::setTelescope(ISD::GDInterface *newTelescope) { currentTelescope = dynamic_cast(newTelescope); syncTelescopeInfo(); } bool Guide::setCamera(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; for (int i = 0; i < guiderCombo->count(); i++) if (device == guiderCombo->itemText(i)) { guiderCombo->setCurrentIndex(i); checkCCD(i); return true; } return false; } QString Guide::camera() { if (currentCCD) return currentCCD->getDeviceName(); return QString(); } void Guide::checkCCD(int ccdNum) { if (guiderType != GUIDE_INTERNAL) return; if (ccdNum == -1) { ccdNum = guiderCombo->currentIndex(); if (ccdNum == -1) return; } if (ccdNum <= CCDs.count()) { currentCCD = CCDs.at(ccdNum); if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) useGuideHead = true; else useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip && targetChip->isCapturing()) return; if (guiderType != GUIDE_INTERNAL) { syncCCDInfo(); return; } //connect(currentCCD, SIGNAL(FITSViewerClosed()), this, &Ekos::Guide::viewerClosed()), Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::numberUpdated, this, &Ekos::Guide::processCCDNumber, Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection); targetChip->setImageView(guideView, FITS_GUIDE); syncCCDInfo(); } } void Guide::syncCCDInfo() { INumberVectorProperty *nvp = nullptr; if (currentCCD == nullptr) return; if (useGuideHead) nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); else nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); if (nvp) { INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); if (np) ccdPixelSizeX = np->value; np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); if (np) ccdPixelSizeY = np->value; np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); if (np) ccdPixelSizeY = np->value; } updateGuideParams(); } void Guide::setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture) { if (primaryFocalLength > 0) focal_length = primaryFocalLength; if (primaryAperture > 0) aperture = primaryAperture; // If we have guide scope info, always prefer that over primary if (guideFocalLength > 0) focal_length = guideFocalLength; if (guideAperture > 0) aperture = guideAperture; updateGuideParams(); } void Guide::syncTelescopeInfo() { if (currentTelescope == nullptr || currentTelescope->isConnected() == false) return; INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); if (nvp) { INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); if (np && np->value > 0) primaryAperture = np->value; np = IUFindNumber(nvp, "GUIDER_APERTURE"); if (np && np->value > 0) guideAperture = np->value; aperture = primaryAperture; //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) aperture = guideAperture; np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH"); if (np && np->value > 0) primaryFL = np->value; np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH"); if (np && np->value > 0) guideFL = np->value; focal_length = primaryFL; //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) focal_length = guideFL; } updateGuideParams(); } void Guide::updateGuideParams() { if (currentCCD == nullptr) return; if (currentCCD->hasGuideHead() == false) useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip == nullptr) { appendLogText(i18n("Connection to the guide CCD is lost.")); return; } if (targetChip->getFrameType() != FRAME_LIGHT) return; if(guiderType == GUIDE_INTERNAL) binningCombo->setEnabled(targetChip->canBin()); int subBinX = 1, subBinY = 1; if (targetChip->canBin()) { int maxBinX, maxBinY; targetChip->getBinning(&subBinX, &subBinY); targetChip->getMaxBin(&maxBinX, &maxBinY); binningCombo->blockSignals(true); binningCombo->clear(); for (int i = 1; i <= maxBinX; i++) binningCombo->addItem(QString("%1x%2").arg(i).arg(i)); binningCombo->setCurrentIndex(subBinX - 1); binningCombo->blockSignals(false); } if (frameSettings.contains(targetChip) == false) { int x, y, w, h; if (targetChip->getFrame(&x, &y, &w, &h)) { if (w > 0 && h > 0) { int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); QVariantMap settings; settings["x"] = Options::guideSubframeEnabled() ? x : minX; settings["y"] = Options::guideSubframeEnabled() ? y : minY; settings["w"] = Options::guideSubframeEnabled() ? w : maxW; settings["h"] = Options::guideSubframeEnabled() ? h : maxH; settings["binx"] = subBinX; settings["biny"] = subBinY; frameSettings[targetChip] = settings; } } } if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1) { FOVScopeCombo->setItemData( ISD::CCD::TELESCOPE_PRIMARY, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(primaryFL / primaryAperture, 'f', 1), QString::number(primaryFL, 'f', 2), QString::number(primaryAperture, 'f', 2)), Qt::ToolTipRole); FOVScopeCombo->setItemData( ISD::CCD::TELESCOPE_GUIDE, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(guideFL / guideAperture, 'f', 1), QString::number(guideFL, 'f', 2), QString::number(guideAperture, 'f', 2)), Qt::ToolTipRole); guider->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, aperture, focal_length); emit guideChipUpdated(targetChip); int x, y, w, h; if (targetChip->getFrame(&x, &y, &w, &h)) { guider->setFrameParams(x, y, w, h, subBinX, subBinY); } l_Focal->setText(QString::number(focal_length, 'f', 1)); l_Aperture->setText(QString::number(aperture, 'f', 1)); if (aperture == 0) { l_FbyD->setText("0"); // Pixel scale in arcsec/pixel pixScaleX = 0; pixScaleY = 0; } else { l_FbyD->setText(QString::number(focal_length / aperture, 'f', 1)); // Pixel scale in arcsec/pixel pixScaleX = 206264.8062470963552 * ccdPixelSizeX / 1000.0 / focal_length; pixScaleY = 206264.8062470963552 * ccdPixelSizeY / 1000.0 / focal_length; } // FOV in arcmin double fov_w = (w * pixScaleX) / 60.0; double fov_h = (h * pixScaleY) / 60.0; l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); } } void Guide::addST4(ISD::ST4 *newST4) { if (guiderType != GUIDE_INTERNAL) return; for (auto &guidePort : ST4List) { if (guidePort->getDeviceName() == newST4->getDeviceName()) return; } ST4List.append(newST4); ST4Combo->addItem(newST4->getDeviceName()); setST4(0); } bool Guide::setST4(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; for (int i = 0; i < ST4List.count(); i++) if (ST4List.at(i)->getDeviceName() == device) { ST4Combo->setCurrentIndex(i); setST4(i); return true; } return false; } QString Guide::st4() { if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) return QString(); return ST4Combo->currentText(); } void Guide::setST4(int index) { if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) return; ST4Driver = ST4List.at(index); GuideDriver = ST4Driver; } void Guide::setAO(ISD::ST4 *newAO) { AODriver = newAO; //guider->setAO(true); } bool Guide::capture() { buildOperationStack(GUIDE_CAPTURE); return executeOperationStack(); } bool Guide::captureOneFrame() { captureTimeout.stop(); if (currentCCD == nullptr) return false; if (currentCCD->isConnected() == false) { appendLogText(i18n("Error: lost connection to CCD.")); return false; } // If CCD Telescope Type does not match desired scope type, change it if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex()) currentCCD->setTelescopeType(static_cast(FOVScopeCombo->currentIndex())); double seqExpose = exposureIN->value(); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->setCaptureMode(FITS_GUIDE); targetChip->setFrameType(FRAME_LIGHT); if (darkFrameCheck->isChecked()) targetChip->setCaptureFilter(FITS_NONE); else targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); guideView->setBaseSize(guideWidget->size()); setBusy(true); if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt()); } currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection); qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; double finalExposure = seqExpose; // Increase exposure for calibration frame if we need auto-select a star // To increase chances we detect one. if (operationStack.contains(GUIDE_STAR_SELECT) && Options::guideAutoStarEnabled()) finalExposure *= 3; // Timeout is exposure duration + timeout threshold in seconds captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD); targetChip->capture(finalExposure); return true; } bool Guide::abort() { if (currentCCD && guiderType == GUIDE_INTERNAL) { captureTimeout.stop(); pulseTimer.stop(); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip->isCapturing()) targetChip->abortExposure(); } manualDitherB->setEnabled(false); setBusy(false); switch (state) { case GUIDE_IDLE: case GUIDE_CONNECTED: case GUIDE_DISCONNECTED: break; case GUIDE_CALIBRATING: case GUIDE_DITHERING: case GUIDE_STAR_SELECT: case GUIDE_CAPTURE: case GUIDE_GUIDING: case GUIDE_LOOPING: guider->abort(); break; default: break; } return true; } void Guide::setBusy(bool enable) { if (enable && pi->isAnimated()) return; else if (enable == false && pi->isAnimated() == false) return; if (enable) { clearCalibrationB->setEnabled(false); guideB->setEnabled(false); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); stopB->setEnabled(true); pi->startAnimation(); //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); } else { if(guiderType != GUIDE_LINGUIDER) { captureB->setEnabled(true); loopB->setEnabled(true); autoStarCheck->setEnabled(true); if(currentCCD) subFrameCheck->setEnabled(true); } if (guiderType == GUIDE_INTERNAL) darkFrameCheck->setEnabled(true); if (calibrationComplete) clearCalibrationB->setEnabled(true); guideB->setEnabled(true); stopB->setEnabled(false); pi->stopAnimation(); connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); } } void Guide::processCaptureTimeout() { auto restartExposure = [&]() { appendLogText(i18n("Exposure timeout. Restarting exposure...")); currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->abortExposure(); targetChip->capture(exposureIN->value()); captureTimeout.start(exposureIN->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); }; m_CaptureTimeoutCounter++; if (m_DeviceRestartCounter >= 3) { m_CaptureTimeoutCounter = 0; m_DeviceRestartCounter = 0; if (state == GUIDE_GUIDING) appendLogText(i18n("Exposure timeout. Aborting Autoguide.")); else if (state == GUIDE_DITHERING) appendLogText(i18n("Exposure timeout. Aborting Dithering.")); else if (state == GUIDE_CALIBRATING) appendLogText(i18n("Exposure timeout. Aborting Calibration.")); abort(); return; } if (m_CaptureTimeoutCounter > 1) { QString camera = currentCCD->getDeviceName(); QString via = ST4Driver ? ST4Driver->getDeviceName() : ""; emit driverTimedout(camera); QTimer::singleShot(5000, [ &, camera, via]() { m_DeviceRestartCounter++; reconnectDriver(camera, via); }); return; } else restartExposure(); } void Guide::reconnectDriver(const QString &camera, const QString &via) { for (auto &oneCamera : CCDs) { if (oneCamera->getDeviceName() == camera) { // Set camera again to the one we restarted guiderCombo->setCurrentIndex(guiderCombo->findText(camera)); ST4Combo->setCurrentIndex(ST4Combo->findText(via)); checkCCD(); // restart capture m_CaptureTimeoutCounter = 0; captureOneFrame(); return; } } QTimer::singleShot(5000, this, [ &, camera, via]() { reconnectDriver(camera, via); }); } void Guide::newFITS(IBLOB *bp) { INDI_UNUSED(bp); captureTimeout.stop(); m_CaptureTimeoutCounter = 0; disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); int subBinX = 1, subBinY = 1; targetChip->getBinning(&subBinX, &subBinY); if (starCenter.x() == 0 && starCenter.y() == 0) { int x = 0, y = 0, w = 0, h = 0; if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; x = settings["x"].toInt(); y = settings["y"].toInt(); w = settings["w"].toInt(); h = settings["h"].toInt(); } else targetChip->getFrame(&x, &y, &w, &h); starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinY)); starCenter.setZ(subBinX); } syncTrackingBoxPosition(); setCaptureComplete(); } void Guide::setCaptureComplete() { DarkLibrary::Instance()->disconnect(this); if (operationStack.isEmpty() == false) { executeOperationStack(); return; } switch (state) { case GUIDE_IDLE: case GUIDE_ABORTED: case GUIDE_CONNECTED: case GUIDE_DISCONNECTED: case GUIDE_CALIBRATION_SUCESS: case GUIDE_CALIBRATION_ERROR: case GUIDE_DITHERING_ERROR: setBusy(false); break; case GUIDE_CAPTURE: state = GUIDE_IDLE; emit newStatus(state); setBusy(false); break; case GUIDE_LOOPING: capture(); break; case GUIDE_CALIBRATING: guider->calibrate(); break; case GUIDE_GUIDING: guider->guide(); break; case GUIDE_DITHERING: guider->dither(Options::ditherPixels()); break; // Feature only of internal guider case GUIDE_MANUAL_DITHERING: dynamic_cast(guider)->processManualDithering(); break; case GUIDE_REACQUIRE: guider->reacquire(); break; case GUIDE_DITHERING_SETTLE: if (Options::ditherNoGuiding()) return; capture(); break; default: break; } emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } void Guide::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS_GUIDE) << text; emit newLog(text); } void Guide::clearLog() { m_LogText.clear(); emit newLog(QString()); } void Guide::setDECSwap(bool enable) { if (ST4Driver == nullptr || guider == nullptr) return; if (guiderType == GUIDE_INTERNAL) { dynamic_cast(guider)->setDECSwap(enable); ST4Driver->setDECSwap(enable); } } bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) { if (GuideDriver == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR)) return false; if (state == GUIDE_CALIBRATING) pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100); return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); } bool Guide::sendPulse(GuideDirection dir, int msecs) { if (GuideDriver == nullptr || dir == NO_DIR) return false; if (state == GUIDE_CALIBRATING) pulseTimer.start(msecs + 100); return GuideDriver->doPulse(dir, msecs); } QStringList Guide::getST4Devices() { QStringList devices; foreach (ISD::ST4 *driver, ST4List) devices << driver->getDeviceName(); return devices; } bool Guide::calibrate() { // Set status to idle and let the operations change it as they get executed state = GUIDE_IDLE; emit newStatus(state); if (guiderType == GUIDE_INTERNAL) { ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (frameSettings.contains(targetChip)) { targetChip->resetFrame(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); QVariantMap settings = frameSettings[targetChip]; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; frameSettings[targetChip] = settings; subFramed = false; } } saveSettings(); buildOperationStack(GUIDE_CALIBRATING); executeOperationStack(); qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); return true; } bool Guide::guide() { auto executeGuide = [this]() { if(guiderType != GUIDE_PHD2) { if (calibrationComplete == false) { calibrate(); return; } } saveSettings(); guider->guide(); //If PHD2 gets a Guide command and it is looping, it will accept a lock position //but if it was not looping it will ignore the lock position and do an auto star automatically //This is not the default behavior in Ekos if auto star is not selected. //This gets around that by noting the position of the tracking box, and enforcing it after the state switches to guide. if(!Options::guideAutoStarEnabled()) { if(guiderType == GUIDE_PHD2 && guideView->isTrackingBoxEnabled()) { double x = starCenter.x(); double y = starCenter.y(); if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() > 50) { guideConnect = connect(this, &Guide::newStatus, this, [this, x, y](Ekos::GuideState newState) { if(newState == GUIDE_GUIDING) { phd2Guider->setLockPosition(x, y); disconnect(guideConnect); } }); } } } } }; if (Options::defaultCaptureCCD() == guiderCombo->currentText()) { connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeGuide]() { //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); KSMessageBox::Instance()->disconnect(this); executeGuide(); }); KSMessageBox::Instance()->questionYesNo( i18n("The guide camera is identical to the primary imaging camera. Are you sure you want to continue?")); return false; } if (m_MountStatus == ISD::Telescope::MOUNT_PARKED) { KSMessageBox::Instance()->sorry(i18n("The mount is parked. Unpark to start guiding.")); return false; } executeGuide(); return true; } bool Guide::dither() { if (Options::ditherNoGuiding() && state == GUIDE_IDLE) { ditherDirectly(); return true; } if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) return true; //This adds a dither text item to the graph where dithering occurred. double time = guideTimer.elapsed() / 1000.0; QCPItemText *ditherLabel = new QCPItemText(driftGraph); ditherLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft); ditherLabel->position->setType(QCPItemPosition::ptPlotCoords); ditherLabel->position->setCoords(time, 1.5); ditherLabel->setColor(Qt::white); ditherLabel->setBrush(Qt::NoBrush); ditherLabel->setPen(Qt::NoPen); ditherLabel->setText("Dither"); ditherLabel->setFont(QFont(font().family(), 10)); if (guiderType == GUIDE_INTERNAL) { if (state != GUIDE_GUIDING) capture(); setStatus(GUIDE_DITHERING); return true; } else return guider->dither(Options::ditherPixels()); } bool Guide::suspend() { if (state == GUIDE_SUSPENDED) return true; else if (state >= GUIDE_CAPTURE) return guider->suspend(); else return false; } bool Guide::resume() { if (state == GUIDE_GUIDING) return true; else if (state == GUIDE_SUSPENDED) return guider->resume(); else return false; } void Guide::setCaptureStatus(CaptureState newState) { switch (newState) { case CAPTURE_DITHERING: dither(); break; default: break; } } void Guide::setPierSide(ISD::Telescope::PierSide newSide) { - Q_UNUSED(newSide); + guider->setPierSide(newSide); // If pier side changes in internal guider // and calibration was already done // then let's swap if (guiderType == GUIDE_INTERNAL && state != GUIDE_GUIDING && state != GUIDE_CALIBRATING && calibrationComplete) { clearCalibration(); appendLogText(i18n("Pier side change detected. Clearing calibration.")); } } void Guide::setMountStatus(ISD::Telescope::Status newState) { m_MountStatus = newState; if (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING) { // reset the calibration if "Always reset calibration" is selected and the mount moves if (Options::resetGuideCalibration()) { appendLogText(i18n("Mount is moving. Resetting calibration...")); clearCalibration(); } // If we're guiding, and the mount either slews or parks, then we abort. if (state == GUIDE_GUIDING || state == GUIDE_DITHERING) { if (newState == ISD::Telescope::MOUNT_PARKING) appendLogText(i18n("Mount is parking. Aborting guide...")); else appendLogText(i18n("Mount is slewing. Aborting guide...")); abort(); } } if (guiderType != GUIDE_INTERNAL) return; switch (newState) { case ISD::Telescope::MOUNT_SLEWING: case ISD::Telescope::MOUNT_PARKING: case ISD::Telescope::MOUNT_MOVING: captureB->setEnabled(false); loopB->setEnabled(false); clearCalibrationB->setEnabled(false); break; default: if (pi->isAnimated() == false) { captureB->setEnabled(true); loopB->setEnabled(true); clearCalibrationB->setEnabled(true); } } } +void Guide::setMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt) +{ + guider->setMountCoords(ra, dec, az, alt); +} + void Guide::setExposure(double value) { exposureIN->setValue(value); } void Guide::setImageFilter(const QString &value) { for (int i = 0; i < filterCombo->count(); i++) if (filterCombo->itemText(i) == value) { filterCombo->setCurrentIndex(i); break; } } void Guide::setCalibrationTwoAxis(bool enable) { Options::setTwoAxisEnabled(enable); } void Guide::setCalibrationAutoStar(bool enable) { autoStarCheck->setChecked(enable); } void Guide::setCalibrationAutoSquareSize(bool enable) { Options::setGuideAutoSquareSizeEnabled(enable); } void Guide::setCalibrationPulseDuration(int pulseDuration) { Options::setCalibrationPulseDuration(pulseDuration); } void Guide::setGuideBoxSizeIndex(int index) { Options::setGuideSquareSizeIndex(index); } void Guide::setGuideAlgorithmIndex(int index) { Options::setGuideAlgorithm(index); } void Guide::setSubFrameEnabled(bool enable) { Options::setGuideSubframeEnabled(enable); if (subFrameCheck->isChecked() != enable) subFrameCheck->setChecked(enable); if(guiderType == GUIDE_PHD2) setExternalGuiderBLOBEnabled(!enable); } void Guide::setDitherSettings(bool enable, double value) { Options::setDitherEnabled(enable); Options::setDitherPixels(value); } void Guide::clearCalibration() { calibrationComplete = false; guider->clearCalibration(); appendLogText(i18n("Calibration is cleared.")); } void Guide::setStatus(Ekos::GuideState newState) { if (newState == state) { // pass through the aborted state if (newState == GUIDE_ABORTED) emit newStatus(state); return; } GuideState previousState = state; state = newState; emit newStatus(state); switch (state) { case GUIDE_CONNECTED: appendLogText(i18n("External guider connected.")); externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(true); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); if(guiderType == GUIDE_PHD2) { captureB->setEnabled(true); loopB->setEnabled(true); autoStarCheck->setEnabled(true); configurePHD2Camera(); setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); boxSizeCombo->setEnabled(true); } break; case GUIDE_DISCONNECTED: appendLogText(i18n("External guider disconnected.")); setBusy(false); //This needs to come before caputureB since it will set it to enabled again. externalConnectB->setEnabled(true); externalDisconnectB->setEnabled(false); clearCalibrationB->setEnabled(false); guideB->setEnabled(false); captureB->setEnabled(false); loopB->setEnabled(false); autoStarCheck->setEnabled(false); boxSizeCombo->setEnabled(false); //setExternalGuiderBLOBEnabled(true); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif break; case GUIDE_CALIBRATION_SUCESS: appendLogText(i18n("Calibration completed.")); calibrationComplete = true; /*if (autoCalibrateGuide) { autoCalibrateGuide = false; guide(); } else setBusy(false);*/ if(guiderType != GUIDE_PHD2) //PHD2 will take care of this. If this command is executed for PHD2, it might start guiding when it is first connected, if the calibration was completed already. guide(); break; case GUIDE_IDLE: case GUIDE_CALIBRATION_ERROR: setBusy(false); manualDitherB->setEnabled(false); break; case GUIDE_CALIBRATING: clearCalibrationGraphs(); appendLogText(i18n("Calibration started.")); setBusy(true); break; case GUIDE_GUIDING: if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) appendLogText(i18n("Guiding resumed.")); else { appendLogText(i18n("Autoguiding started.")); setBusy(true); clearGuideGraphs(); guideTimer = QTime::currentTime(); refreshColorScheme(); } manualDitherB->setEnabled(true); break; case GUIDE_ABORTED: appendLogText(i18n("Autoguiding aborted.")); setBusy(false); break; case GUIDE_SUSPENDED: appendLogText(i18n("Guiding suspended.")); break; case GUIDE_REACQUIRE: capture(); break; case GUIDE_MANUAL_DITHERING: appendLogText(i18n("Manual dithering in progress.")); break; case GUIDE_DITHERING: appendLogText(i18n("Dithering in progress.")); break; case GUIDE_DITHERING_SETTLE: if (Options::ditherSettle() > 0) appendLogText(i18np("Post-dither settling for %1 second...", "Post-dither settling for %1 seconds...", Options::ditherSettle())); capture(); break; case GUIDE_DITHERING_ERROR: appendLogText(i18n("Dithering failed.")); // LinGuider guide continue after dithering failure if (guiderType != GUIDE_LINGUIDER) { //state = GUIDE_IDLE; state = GUIDE_ABORTED; setBusy(false); } break; case GUIDE_DITHERING_SUCCESS: appendLogText(i18n("Dithering completed successfully.")); // Go back to guiding state immediately if using regular guider if (Options::ditherNoGuiding() == false) { setStatus(GUIDE_GUIDING); // Only capture again if we are using internal guider if (guiderType == GUIDE_INTERNAL) capture(); } break; default: break; } } void Guide::updateCCDBin(int index) { if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL) return; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->setBinning(index + 1, index + 1); QVariantMap settings = frameSettings[targetChip]; settings["binx"] = index + 1; settings["biny"] = index + 1; frameSettings[targetChip] = settings; guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), settings["binx"].toInt(), settings["biny"].toInt()); } void Guide::processCCDNumber(INumberVectorProperty *nvp) { if (currentCCD == nullptr || (nvp->device != currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) return; if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead)) { binningCombo->disconnect(); binningCombo->setCurrentIndex(nvp->np[0].value - 1); connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin); } } void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) { if (guiderType != GUIDE_INTERNAL) return; INDI_UNUSED(exposure); if (expState == IPS_ALERT && ((state == GUIDE_GUIDING) || (state == GUIDE_DITHERING) || (state == GUIDE_CALIBRATING))) { appendLogText(i18n("Exposure failed. Restarting exposure...")); currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); targetChip->capture(exposureIN->value()); } } void Guide::setDarkFrameEnabled(bool enable) { Options::setGuideDarkFrameEnabled(enable); if (darkFrameCheck->isChecked() != enable) darkFrameCheck->setChecked(enable); } void Guide::saveDefaultGuideExposure() { Options::setGuideExposure(exposureIN->value()); if(guiderType == GUIDE_PHD2) phd2Guider->requestSetExposureTime(exposureIN->value() * 1000); } void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) { starCenter.setX(newCenter.x()); starCenter.setY(newCenter.y()); if (newCenter.z() > 0) starCenter.setZ(newCenter.z()); if (updateNow) syncTrackingBoxPosition(); } void Guide::syncTrackingBoxPosition() { if(!currentCCD || guiderType == GUIDE_LINGUIDER) return; if(guiderType == GUIDE_PHD2) { //This way it won't set the tracking box on the Guide Star Image. if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() < 50) { guideView->setTrackingBoxEnabled(false); return; } } } ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); Q_ASSERT(targetChip); int subBinX = 1, subBinY = 1; targetChip->getBinning(&subBinX, &subBinY); if (starCenter.isNull() == false) { double boxSize = boxSizeCombo->currentText().toInt(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); // If box size is larger than image size, set it to lower index if (boxSize / subBinX >= w || boxSize / subBinY >= h) { int newIndex = boxSizeCombo->currentIndex() - 1; if (newIndex >= 0) boxSizeCombo->setCurrentIndex(newIndex); return; } // If binning changed, update coords accordingly if (subBinX != starCenter.z()) { if (starCenter.z() > 0) { starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); } starCenter.setZ(subBinX); } QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), boxSize / subBinX, boxSize / subBinY); guideView->setTrackingBoxEnabled(true); guideView->setTrackingBox(starRect); } } bool Guide::setGuiderType(int type) { // Use default guider option if (type == -1) type = Options::guiderType(); else if (type == guiderType) return true; if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING) { appendLogText(i18n("Cannot change guider type while active.")); return false; } if (guider != nullptr) { // Disconnect from host if (guider->isConnected()) guider->Disconnect(); // Disconnect signals guider->disconnect(); } guiderType = static_cast(type); switch (type) { case GUIDE_INTERNAL: { connect(internalGuider, SIGNAL(newPulse(GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int))); connect(internalGuider, SIGNAL(newPulse(GuideDirection, int, GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int, GuideDirection, int))); connect(internalGuider, SIGNAL(DESwapChanged(bool)), swapCheck, SLOT(setChecked(bool))); connect(internalGuider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); guider = internalGuider; internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); internalGuider->setRegionAxis(opsGuide->kcfg_GuideRegionAxis->currentText().toInt()); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); captureB->setEnabled(true); loopB->setEnabled(true); darkFrameCheck->setEnabled(true); subFrameCheck->setEnabled(true); autoStarCheck->setEnabled(true); guiderCombo->setEnabled(true); ST4Combo->setEnabled(true); exposureIN->setEnabled(true); binningCombo->setEnabled(true); boxSizeCombo->setEnabled(true); filterCombo->setEnabled(true); externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(false); controlGroup->setEnabled(true); infoGroup->setEnabled(true); label_6->setEnabled(true); FOVScopeCombo->setEnabled(true); l_3->setEnabled(true); spinBox_GuideRate->setEnabled(true); l_RecommendedGain->setEnabled(true); l_5->setEnabled(true); l_6->setEnabled(true); l_7->setEnabled(true); l_8->setEnabled(true); l_Aperture->setEnabled(true); l_FOV->setEnabled(true); l_FbyD->setEnabled(true); l_Focal->setEnabled(true); driftGraphicsGroup->setEnabled(true); guiderCombo->setToolTip(i18n("Select guide camera.")); updateGuideParams(); } break; case GUIDE_PHD2: if (phd2Guider.isNull()) phd2Guider = new PHD2(); guider = phd2Guider; phd2Guider->setGuideView(guideView); connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); clearCalibrationB->setEnabled(true); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); guideB->setEnabled(false); //This will be enabled later when equipment connects (or not) externalConnectB->setEnabled(false); checkBox_DirRA->setEnabled(false); eastControlCheck->setEnabled(false); westControlCheck->setEnabled(false); swapCheck->setEnabled(false); controlGroup->setEnabled(false); infoGroup->setEnabled(true); label_6->setEnabled(false); FOVScopeCombo->setEnabled(false); l_3->setEnabled(false); spinBox_GuideRate->setEnabled(false); l_RecommendedGain->setEnabled(false); l_5->setEnabled(false); l_6->setEnabled(false); l_7->setEnabled(false); l_8->setEnabled(false); l_Aperture->setEnabled(false); l_FOV->setEnabled(false); l_FbyD->setEnabled(false); l_Focal->setEnabled(false); driftGraphicsGroup->setEnabled(true); ST4Combo->setEnabled(false); exposureIN->setEnabled(true); binningCombo->setEnabled(false); boxSizeCombo->setEnabled(false); filterCombo->setEnabled(false); guiderCombo->setEnabled(false); if (Options::resetGuideCalibration()) appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); updateGuideParams(); break; case GUIDE_LINGUIDER: if (linGuider.isNull()) linGuider = new LinGuider(); guider = linGuider; clearCalibrationB->setEnabled(true); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); guideB->setEnabled(true); externalConnectB->setEnabled(true); controlGroup->setEnabled(false); infoGroup->setEnabled(false); driftGraphicsGroup->setEnabled(false); ST4Combo->setEnabled(false); exposureIN->setEnabled(false); binningCombo->setEnabled(false); boxSizeCombo->setEnabled(false); filterCombo->setEnabled(false); guiderCombo->setEnabled(false); updateGuideParams(); break; } if (guider != nullptr) { connect(guider, &Ekos::GuideInterface::frameCaptureRequested, this, &Ekos::Guide::capture); connect(guider, &Ekos::GuideInterface::newLog, this, &Ekos::Guide::appendLogText); connect(guider, &Ekos::GuideInterface::newStatus, this, &Ekos::Guide::setStatus); connect(guider, &Ekos::GuideInterface::newStarPosition, this, &Ekos::Guide::setStarPosition); connect(guider, &Ekos::GuideInterface::newAxisDelta, this, &Ekos::Guide::setAxisDelta); connect(guider, &Ekos::GuideInterface::newAxisPulse, this, &Ekos::Guide::setAxisPulse); connect(guider, &Ekos::GuideInterface::newAxisSigma, this, &Ekos::Guide::setAxisSigma); connect(guider, &Ekos::GuideInterface::calibrationUpdate, this, &Ekos::Guide::calibrationUpdate); connect(guider, &Ekos::GuideInterface::guideEquipmentUpdated, this, &Ekos::Guide::configurePHD2Camera); } externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(false); if (guider != nullptr && guiderType != GUIDE_INTERNAL) { externalConnectB->setEnabled(!guider->isConnected()); externalDisconnectB->setEnabled(guider->isConnected()); } if (guider != nullptr) guider->Connect(); return true; } void Guide::updateTrackingBoxSize(int currentIndex) { if (currentIndex >= 0) { Options::setGuideSquareSizeIndex(currentIndex); if (guiderType == GUIDE_INTERNAL) dynamic_cast(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt()); syncTrackingBoxPosition(); } } /* void Guide::onXscaleChanged( int i ) { int rx, ry; driftGraphics->getVisibleRanges( &rx, &ry ); driftGraphics->setVisibleRanges( i*driftGraphics->getGridN(), ry ); driftGraphics->update(); } void Guide::onYscaleChanged( int i ) { int rx, ry; driftGraphics->getVisibleRanges( &rx, &ry ); driftGraphics->setVisibleRanges( rx, i*driftGraphics->getGridN() ); driftGraphics->update(); } */ void Guide::onThresholdChanged(int index) { switch (guiderType) { case GUIDE_INTERNAL: dynamic_cast(guider)->setSquareAlgorithm(index); break; default: break; } } void Guide::onInfoRateChanged(double val) { Options::setGuidingRate(val); double gain = 0; if (val > 0.01) gain = 1000.0 / (val * 15.0); l_RecommendedGain->setText(i18n("P: %1", QString().setNum(gain, 'f', 2))); } void Guide::onEnableDirRA(bool enable) { Options::setRAGuideEnabled(enable); } void Guide::onEnableDirDEC(bool enable) { Options::setDECGuideEnabled(enable); updatePHD2Directions(); } void Guide::syncSettings() { QSpinBox *pSB = nullptr; QDoubleSpinBox *pDSB = nullptr; QCheckBox *pCB = nullptr; QObject *obj = sender(); if ((pSB = qobject_cast(obj))) { if (pSB == spinBox_MaxPulseRA) Options::setRAMaximumPulse(pSB->value()); else if (pSB == spinBox_MaxPulseDEC) Options::setDECMaximumPulse(pSB->value()); else if (pSB == spinBox_MinPulseRA) Options::setRAMinimumPulse(pSB->value()); else if (pSB == spinBox_MinPulseDEC) Options::setDECMinimumPulse(pSB->value()); } else if ((pDSB = qobject_cast(obj))) { if (pDSB == spinBox_PropGainRA) Options::setRAProportionalGain(pDSB->value()); else if (pDSB == spinBox_PropGainDEC) Options::setDECProportionalGain(pDSB->value()); else if (pDSB == spinBox_IntGainRA) Options::setRAIntegralGain(pDSB->value()); else if (pDSB == spinBox_IntGainDEC) Options::setDECIntegralGain(pDSB->value()); else if (pDSB == spinBox_DerGainRA) Options::setRADerivativeGain(pDSB->value()); else if (pDSB == spinBox_DerGainDEC) Options::setDECDerivativeGain(pDSB->value()); } else if ((pCB = qobject_cast(obj))) { if (pCB == autoStarCheck) Options::setGuideAutoStarEnabled(pCB->isChecked()); } } void Guide::onControlDirectionChanged(bool enable) { QObject *obj = sender(); if (northControlCheck == dynamic_cast(obj)) { Options::setNorthDECGuideEnabled(enable); updatePHD2Directions(); } else if (southControlCheck == dynamic_cast(obj)) { Options::setSouthDECGuideEnabled(enable); updatePHD2Directions(); } else if (westControlCheck == dynamic_cast(obj)) { Options::setWestRAGuideEnabled(enable); } else if (eastControlCheck == dynamic_cast(obj)) { Options::setEastRAGuideEnabled(enable); } } void Guide::updatePHD2Directions() { if(guiderType == GUIDE_PHD2) phd2Guider -> requestSetDEGuideMode(checkBox_DirDEC->isChecked(), northControlCheck->isChecked(), southControlCheck->isChecked()); } void Guide::updateDirectionsFromPHD2(QString mode) { //disable connections disconnect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); disconnect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); disconnect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); if(mode == "Auto") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(true); southControlCheck->setChecked(true); } else if(mode == "North") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(true); southControlCheck->setChecked(false); } else if(mode == "South") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(false); southControlCheck->setChecked(true); } else //Off { checkBox_DirDEC->setChecked(false); northControlCheck->setChecked(true); southControlCheck->setChecked(true); } //Re-enable connections connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); } void Guide::loadSettings() { // Exposure exposureIN->setValue(Options::guideExposure()); // Box Size boxSizeCombo->setCurrentIndex(Options::guideSquareSizeIndex()); // Dark frame? darkFrameCheck->setChecked(Options::guideDarkFrameEnabled()); // Subframed? subFrameCheck->setChecked(Options::guideSubframeEnabled()); // Guiding Rate spinBox_GuideRate->setValue(Options::guidingRate()); // RA/DEC enabled? checkBox_DirRA->setChecked(Options::rAGuideEnabled()); checkBox_DirDEC->setChecked(Options::dECGuideEnabled()); // N/S enabled? northControlCheck->setChecked(Options::northDECGuideEnabled()); southControlCheck->setChecked(Options::southDECGuideEnabled()); // W/E enabled? westControlCheck->setChecked(Options::westRAGuideEnabled()); eastControlCheck->setChecked(Options::eastRAGuideEnabled()); // PID Control - Proportional Gain spinBox_PropGainRA->setValue(Options::rAProportionalGain()); spinBox_PropGainDEC->setValue(Options::dECProportionalGain()); // PID Control - Integral Gain spinBox_IntGainRA->setValue(Options::rAIntegralGain()); spinBox_IntGainDEC->setValue(Options::dECIntegralGain()); // PID Control - Derivative Gain spinBox_DerGainRA->setValue(Options::rADerivativeGain()); spinBox_DerGainDEC->setValue(Options::dECDerivativeGain()); // Max Pulse Duration (ms) spinBox_MaxPulseRA->setValue(Options::rAMaximumPulse()); spinBox_MaxPulseDEC->setValue(Options::dECMaximumPulse()); // Min Pulse Duration (ms) spinBox_MinPulseRA->setValue(Options::rAMinimumPulse()); spinBox_MinPulseDEC->setValue(Options::dECMinimumPulse()); // Autostar autoStarCheck->setChecked(Options::guideAutoStarEnabled()); } void Guide::saveSettings() { // Exposure Options::setGuideExposure(exposureIN->value()); // Box Size Options::setGuideSquareSizeIndex(boxSizeCombo->currentIndex()); // Dark frame? Options::setGuideDarkFrameEnabled(darkFrameCheck->isChecked()); // Subframed? Options::setGuideSubframeEnabled(subFrameCheck->isChecked()); // Guiding Rate? Options::setGuidingRate(spinBox_GuideRate->value()); // RA/DEC enabled? Options::setRAGuideEnabled(checkBox_DirRA->isChecked()); Options::setDECGuideEnabled(checkBox_DirDEC->isChecked()); // N/S enabled? Options::setNorthDECGuideEnabled(northControlCheck->isChecked()); Options::setSouthDECGuideEnabled(southControlCheck->isChecked()); // W/E enabled? Options::setWestRAGuideEnabled(westControlCheck->isChecked()); Options::setEastRAGuideEnabled(eastControlCheck->isChecked()); // PID Control - Proportional Gain Options::setRAProportionalGain(spinBox_PropGainRA->value()); Options::setDECProportionalGain(spinBox_PropGainDEC->value()); // PID Control - Integral Gain Options::setRAIntegralGain(spinBox_IntGainRA->value()); Options::setDECIntegralGain(spinBox_IntGainDEC->value()); // PID Control - Derivative Gain Options::setRADerivativeGain(spinBox_DerGainRA->value()); Options::setDECDerivativeGain(spinBox_DerGainDEC->value()); // Max Pulse Duration (ms) Options::setRAMaximumPulse(spinBox_MaxPulseRA->value()); Options::setDECMaximumPulse(spinBox_MaxPulseDEC->value()); // Min Pulse Duration (ms) Options::setRAMinimumPulse(spinBox_MinPulseRA->value()); Options::setDECMinimumPulse(spinBox_MinPulseDEC->value()); } void Guide::setTrackingStar(int x, int y) { QVector3D newStarPosition(x, y, -1); setStarPosition(newStarPosition, true); if(guiderType == GUIDE_PHD2) { //The Guide Star Image is 32 pixels across or less, so this guarantees it isn't that. if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() > 50) phd2Guider->setLockPosition(starCenter.x(), starCenter.y()); } } /*if (state == GUIDE_STAR_SELECT) { guider->setStarPosition(newStarPosition); guider->calibrate(); }*/ if (operationStack.isEmpty() == false) executeOperationStack(); } void Guide::setAxisDelta(double ra, double de) { //If PHD2 starts guiding because somebody pusted the button remotely, we want to set the state to guiding. //If guide pulses start coming in, it must be guiding. // 2020-04-10 sterne-jaeger: Will be resolved inside EKOS phd guiding. // if(guiderType == GUIDE_PHD2 && state != GUIDE_GUIDING) // setStatus(GUIDE_GUIDING); // Time since timer started. double key = guideTimer.elapsed() / 1000.0; ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. driftGraph->graph(0)->addData(key, ra); driftGraph->graph(1)->addData(key, de); int currentNumPoints = driftGraph->graph(0)->dataCount(); guideSlider->setMaximum(currentNumPoints); if(graphOnLatestPt) guideSlider->setValue(currentNumPoints); // Expand range if it doesn't fit already if (driftGraph->yAxis->range().contains(ra) == false) driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); if (driftGraph->yAxis->range().contains(de) == false) driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); // Show last 120 seconds //driftGraph->xAxis->setRange(key, 120, Qt::AlignRight); if(graphOnLatestPt) { driftGraph->xAxis->setRange(key, driftGraph->xAxis->range().size(), Qt::AlignRight); driftGraph->graph(2)->data()->clear(); //Clear highlighted RA point driftGraph->graph(3)->data()->clear(); //Clear highlighted DEC point driftGraph->graph(2)->addData(key, ra); //Set highlighted RA point to latest point driftGraph->graph(3)->addData(key, de); //Set highlighted DEC point to latest point } driftGraph->replot(); //Add to Drift Plot driftPlot->graph(0)->addData(ra, de); if(graphOnLatestPt) { driftPlot->graph(1)->data()->clear(); //Clear highlighted point driftPlot->graph(1)->addData(ra, de); //Set highlighted point to latest point } if (driftPlot->xAxis->range().contains(ra) == false || driftPlot->yAxis->range().contains(de) == false) { driftPlot->setBackground(QBrush(Qt::gray)); QTimer::singleShot(300, this, [ = ]() { driftPlot->setBackground(QBrush(Qt::black)); driftPlot->replot(); }); } driftPlot->replot(); l_DeltaRA->setText(QString::number(ra, 'f', 2)); l_DeltaDEC->setText(QString::number(de, 'f', 2)); emit newAxisDelta(ra, de); profilePixmap = driftGraph->grab(); emit newProfilePixmap(profilePixmap); } void Guide::calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString& message, double dx, double dy) { switch (type) { case GuideInterface::RA_IN: calibrationPlot->graph(0)->addData(dx, dy); break; case GuideInterface::RA_OUT: calibrationPlot->graph(1)->addData(dx, dy); break; case GuideInterface::DEC_IN: calibrationPlot->graph(2)->addData(dx, dy); break; case GuideInterface::DEC_OUT: calibrationPlot->graph(3)->addData(dx, dy); break; case GuideInterface::CALIBRATION_MESSAGE_ONLY: ; } calLabel->setText(message); calibrationPlot->replot(); } void Guide::setAxisSigma(double ra, double de) { l_ErrRA->setText(QString::number(ra, 'f', 2)); l_ErrDEC->setText(QString::number(de, 'f', 2)); l_TotalRMS->setText(QString::number(std::hypot(ra, de), 'f', 2)); emit newAxisSigma(ra, de); } QList Guide::axisDelta() { QList delta; delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); return delta; } QList Guide::axisSigma() { QList sigma; sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); return sigma; } void Guide::setAxisPulse(double ra, double de) { l_PulseRA->setText(QString::number(static_cast(ra))); l_PulseDEC->setText(QString::number(static_cast(de))); double key = guideTimer.elapsed() / 1000.0; driftGraph->graph(4)->addData(key, ra); driftGraph->graph(5)->addData(key, de); } void Guide::refreshColorScheme() { // Drift color legend if (driftGraph) { if (driftGraph->graph(0) && driftGraph->graph(1) && driftGraph->graph(2) && driftGraph->graph(3) && driftGraph->graph(4) && driftGraph->graph(5)) { driftGraph->graph(0)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(1)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(2)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10)); driftGraph->graph(3)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10)); QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); raPulseColor.setAlpha(75); driftGraph->graph(4)->setPen(QPen(raPulseColor)); driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); dePulseColor.setAlpha(75); driftGraph->graph(5)->setPen(QPen(dePulseColor)); driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); } } } void Guide::driftMouseClicked(QMouseEvent *event) { if (event->buttons() & Qt::RightButton) { driftGraph->yAxis->setRange(-3, 3); } } void Guide::driftMouseOverLine(QMouseEvent *event) { double key = driftGraph->xAxis->pixelToCoord(event->localPos().x()); if (driftGraph->xAxis->range().contains(key)) { QCPGraph *graph = qobject_cast(driftGraph->plottableAt(event->pos(), false)); if (graph) { int raIndex = driftGraph->graph(0)->findBegin(key); int deIndex = driftGraph->graph(1)->findBegin(key); double raDelta = driftGraph->graph(0)->dataMainValue(raIndex); double deDelta = driftGraph->graph(1)->dataMainValue(deIndex); double raPulse = driftGraph->graph(4)->dataMainValue(raIndex); //Get RA Pulse from RA pulse data double dePulse = driftGraph->graph(5)->dataMainValue(deIndex); //Get DEC Pulse from DEC pulse data // Compute time value: QTime localTime = guideTimer; localTime = localTime.addSecs(key); QToolTip::hideText(); if(raPulse == 0 && dePulse == 0) { QToolTip::showText( event->globalPos(), i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds;", "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
", localTime.toString("hh:mm:ss AP"), QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2))); } else { QToolTip::showText( event->globalPos(), i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is RA Pulse in ms; %5 is DE Pulse in ms", "" "" "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
RA Pulse: %4 ms
DE Pulse: %5 ms
", localTime.toString("hh:mm:ss AP"), QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2), QString::number(raPulse, 'f', 2), QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph. } } else QToolTip::hideText(); driftGraph->replot(); } } void Guide::buildOperationStack(GuideState operation) { operationStack.clear(); switch (operation) { case GUIDE_CAPTURE: if (Options::guideDarkFrameEnabled()) operationStack.push(GUIDE_DARK); operationStack.push(GUIDE_CAPTURE); operationStack.push(GUIDE_SUBFRAME); break; case GUIDE_CALIBRATING: operationStack.push(GUIDE_CALIBRATING); if (guiderType == GUIDE_INTERNAL) { if (Options::guideDarkFrameEnabled()) operationStack.push(GUIDE_DARK); // Auto Star Selected Path if (Options::guideAutoStarEnabled()) { // If subframe is enabled and we need to auto select a star, then we need to make the final capture // of the subframed image. This is only done if we aren't already subframed. if (subFramed == false && Options::guideSubframeEnabled()) operationStack.push(GUIDE_CAPTURE); // Do not subframe and auto-select star on Image Guiding mode if (Options::imageGuidingEnabled() == false) { operationStack.push(GUIDE_SUBFRAME); operationStack.push(GUIDE_STAR_SELECT); } operationStack.push(GUIDE_CAPTURE); // If we are being ask to go full frame, let's do that first if (subFramed == true && Options::guideSubframeEnabled() == false) operationStack.push(GUIDE_SUBFRAME); } // Manual Star Selection Path else { // In Image Guiding, we never need to subframe if (Options::imageGuidingEnabled() == false) { // Final capture before we start calibrating if (subFramed == false && Options::guideSubframeEnabled()) operationStack.push(GUIDE_CAPTURE); // Subframe if required operationStack.push(GUIDE_SUBFRAME); } // First capture an image operationStack.push(GUIDE_CAPTURE); } } break; default: break; } } bool Guide::executeOperationStack() { if (operationStack.isEmpty()) return false; GuideState nextOperation = operationStack.pop(); bool actionRequired = false; switch (nextOperation) { case GUIDE_SUBFRAME: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_DARK: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_CAPTURE: actionRequired = captureOneFrame(); break; case GUIDE_STAR_SELECT: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_CALIBRATING: if (guiderType == GUIDE_INTERNAL) { guider->setStarPosition(starCenter); dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); // No need to calibrate if (Options::imageGuidingEnabled()) { setStatus(GUIDE_CALIBRATION_SUCESS); break; } // Tracking must be engaged if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) currentTelescope->setTrackEnabled(true); } if (guider->calibrate()) { if (guiderType == GUIDE_INTERNAL) disconnect(guideView, SIGNAL(trackingStarSelected(int, int)), this, SLOT(setTrackingStar(int, int))); setBusy(true); } else { emit newStatus(GUIDE_CALIBRATION_ERROR); state = GUIDE_IDLE; appendLogText(i18n("Calibration failed to start.")); setBusy(false); } break; default: break; } // If an additional action is required, return return and continue later if (actionRequired) return true; // Otherwise, continue processing the stack else return executeOperationStack(); } bool Guide::executeOneOperation(GuideState operation) { bool actionRequired = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); int subBinX, subBinY; targetChip->getBinning(&subBinX, &subBinY); switch (operation) { case GUIDE_SUBFRAME: { // Check if we need and can subframe if (subFramed == false && Options::guideSubframeEnabled() == true && targetChip->canSubframe()) { int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); int offset = boxSizeCombo->currentText().toInt() / subBinX; int x = starCenter.x(); int y = starCenter.y(); x = (x - offset * 2) * subBinX; y = (y - offset * 2) * subBinY; int w = offset * 4 * subBinX; int h = offset * 4 * subBinY; if (x < minX) x = minX; if (y < minY) y = minY; if ((x + w) > maxW) w = maxW - x; if ((y + h) > maxH) h = maxH - y; targetChip->setFrame(x, y, w, h); subFramed = true; QVariantMap settings = frameSettings[targetChip]; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; settings["binx"] = subBinX; settings["biny"] = subBinY; frameSettings[targetChip] = settings; starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinX)); } // Otherwise check if we are already subframed // and we need to go back to full frame // or if we need to go back to full frame since we need // to reaquire a star else if (subFramed && (Options::guideSubframeEnabled() == false || state == GUIDE_REACQUIRE)) { targetChip->resetFrame(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); QVariantMap settings; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; settings["binx"] = 1; settings["biny"] = 1; frameSettings[targetChip] = settings; subFramed = false; starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinX)); //starCenter.setX(0); //starCenter.setY(0); } } break; case GUIDE_DARK: { // Do we need to take a dark frame? if (Options::guideDarkFrameEnabled()) { QVariantMap settings = frameSettings[targetChip]; uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); FITSData *darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, [&](bool completed) { DarkLibrary::Instance()->disconnect(this); if (completed != darkFrameCheck->isChecked()) setDarkFrameEnabled(completed); if (completed) setCaptureComplete(); else abort(); }); connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Guide::appendLogText); actionRequired = true; targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); if (darkData) DarkLibrary::Instance()->subtract(darkData, guideView, targetChip->getCaptureFilter(), offsetX, offsetY); else { DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), offsetX, offsetY); } } } break; case GUIDE_STAR_SELECT: { state = GUIDE_STAR_SELECT; emit newStatus(state); if (Options::guideAutoStarEnabled()) { bool autoStarCaptured = internalGuider->selectAutoStar(); if (autoStarCaptured) { appendLogText(i18n("Auto star selected.")); } else { appendLogText(i18n("Failed to select an auto star.")); actionRequired = true; state = GUIDE_CALIBRATION_ERROR; emit newStatus(state); setBusy(false); } } else { appendLogText(i18n("Select a guide star to calibrate.")); actionRequired = true; } } break; default: break; } return actionRequired; } void Guide::processGuideOptions() { if (Options::guiderType() != guiderType) { guiderType = static_cast(Options::guiderType()); setGuiderType(Options::guiderType()); } } void Guide::showFITSViewer() { FITSData *data = guideView->getImageData(); if (data) { QUrl url = QUrl::fromLocalFile(data->filename()); if (fv.isNull()) { if (Options::singleWindowCapturedFITS()) fv = KStars::Instance()->genericFITSViewer(); else { fv = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); KStars::Instance()->addFITSViewer(fv); } fv->addFITS(url); FITSView *currentView = fv->getCurrentView(); if (currentView) currentView->getImageData()->setAutoRemoveTemporaryFITS(false); } else fv->updateFITS(url, 0); fv->show(); } } void Guide::setExternalGuiderBLOBEnabled(bool enable) { // Nothing to do if guider is internal if (guiderType == GUIDE_INTERNAL) return; if(!currentCCD) return; currentCCD->setBLOBEnabled(enable); if(currentCCD->isBLOBEnabled()) { if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) useGuideHead = true; else useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip) { targetChip->setImageView(guideView, FITS_GUIDE); targetChip->setCaptureMode(FITS_GUIDE); } syncCCDInfo(); } } void Guide::ditherDirectly() { double ditherPulse = Options::ditherNoGuidingPulse(); // Randomize pulse length. It is equal to 50% of pulse length + random value up to 50% // e.g. if ditherPulse is 500ms then final pulse is = 250 + rand(0 to 250) int ra_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); int ra_polarity = (rand() % 2 == 0) ? 1 : -1; int de_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); int de_polarity = (rand() % 2 == 0) ? 1 : -1; qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither..."; qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << ra_msec << "ra_polarity:" << ra_polarity << "de_msec:" << de_msec << "de_polarity:" << de_polarity; bool rc = sendPulse(ra_polarity > 0 ? RA_INC_DIR : RA_DEC_DIR, ra_msec, de_polarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR, de_msec); if (rc) { qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful."; QTimer::singleShot( (ra_msec > de_msec ? ra_msec : de_msec) + Options::ditherSettle() * 1000 + 100, [this]() { emit newStatus(GUIDE_DITHERING_SUCCESS); state = GUIDE_IDLE; }); } else { qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed."; emit newStatus(GUIDE_DITHERING_ERROR); state = GUIDE_IDLE; } } void Guide::updateTelescopeType(int index) { if (currentCCD == nullptr) return; focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; Options::setGuideScopeType(index); syncTelescopeInfo(); } void Guide::setDefaultST4(const QString &driver) { Options::setDefaultST4Driver(driver); } void Guide::setDefaultCCD(const QString &ccd) { if (guiderType == GUIDE_INTERNAL) Options::setDefaultGuideCCD(ccd); } void Guide::handleManualDither() { ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip == nullptr) return; Ui::ManualDither ditherDialog; QDialog container(this); ditherDialog.setupUi(&container); if (guiderType != GUIDE_INTERNAL) { ditherDialog.coordinatesR->setEnabled(false); ditherDialog.x->setEnabled(false); ditherDialog.y->setEnabled(false); } int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); ditherDialog.x->setMinimum(minX); ditherDialog.x->setMaximum(maxX); ditherDialog.y->setMinimum(minY); ditherDialog.y->setMaximum(maxY); ditherDialog.x->setValue(starCenter.x()); ditherDialog.y->setValue(starCenter.y()); if (container.exec() == QDialog::Accepted) { if (ditherDialog.magnitudeR->isChecked()) guider->dither(ditherDialog.magnitude->value()); else { dynamic_cast(guider)->ditherXY(ditherDialog.x->value(), ditherDialog.y->value()); } } } bool Guide::connectGuider() { return guider->Connect(); } bool Guide::disconnectGuider() { return guider->Disconnect(); } void Guide::initPlots() { initDriftGraph(); initDriftPlot(); initCalibrationPlot(); connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); //This sets the values of all the Graph Options that are stored. accuracyRadiusSpin->setValue(Options::guiderAccuracyThreshold()); showRAPlotCheck->setChecked(Options::rADisplayedOnGuideGraph()); showDECPlotCheck->setChecked(Options::dEDisplayedOnGuideGraph()); showRACorrectionsCheck->setChecked(Options::rACorrDisplayedOnGuideGraph()); showDECorrectionsCheck->setChecked(Options::dECorrDisplayedOnGuideGraph()); buildTarget(); } void Guide::initDriftGraph() { // Drift Graph Color Settings driftGraph->setBackground(QBrush(Qt::black)); driftGraph->xAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftGraph->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftGraph->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftGraph->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftGraph->xAxis->grid()->setZeroLinePen(Qt::NoPen); driftGraph->yAxis->grid()->setZeroLinePen(QPen(Qt::white, 1)); driftGraph->xAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis2->setBasePen(QPen(Qt::white, 1)); driftGraph->xAxis->setTickPen(QPen(Qt::white, 1)); driftGraph->yAxis->setTickPen(QPen(Qt::white, 1)); driftGraph->yAxis2->setTickPen(QPen(Qt::white, 1)); driftGraph->xAxis->setSubTickPen(QPen(Qt::white, 1)); driftGraph->yAxis->setSubTickPen(QPen(Qt::white, 1)); driftGraph->yAxis2->setSubTickPen(QPen(Qt::white, 1)); driftGraph->xAxis->setTickLabelColor(Qt::white); driftGraph->yAxis->setTickLabelColor(Qt::white); driftGraph->yAxis2->setTickLabelColor(Qt::white); driftGraph->xAxis->setLabelColor(Qt::white); driftGraph->yAxis->setLabelColor(Qt::white); driftGraph->yAxis2->setLabelColor(Qt::white); //Horizontal Axis Time Ticker Settings QSharedPointer timeTicker(new QCPAxisTickerTime); timeTicker->setTimeFormat("%m:%s"); driftGraph->xAxis->setTicker(timeTicker); //Vertical Axis Labels Settings driftGraph->yAxis2->setVisible(true); driftGraph->yAxis2->setTickLabels(true); driftGraph->yAxis->setLabelFont(QFont(font().family(), 10)); driftGraph->yAxis2->setLabelFont(QFont(font().family(), 10)); driftGraph->yAxis->setTickLabelFont(QFont(font().family(), 9)); driftGraph->yAxis2->setTickLabelFont(QFont(font().family(), 9)); driftGraph->yAxis->setLabelPadding(1); driftGraph->yAxis2->setLabelPadding(1); driftGraph->yAxis->setLabel(i18n("drift (arcsec)")); driftGraph->yAxis2->setLabel(i18n("pulse (ms)")); setupNSEWLabels(); //Sets the default ranges driftGraph->xAxis->setRange(0, 60, Qt::AlignRight); driftGraph->yAxis->setRange(-3, 3); int scale = 50; //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg correctionSlider->setValue(scale); driftGraph->yAxis2->setRange(-3 * scale, 3 * scale); //This sets up the legend driftGraph->legend->setVisible(true); driftGraph->legend->setFont(QFont("Helvetica", 9)); driftGraph->legend->setTextColor(Qt::white); driftGraph->legend->setBrush(QBrush(Qt::black)); driftGraph->legend->setFillOrder(QCPLegend::foColumnsFirst); driftGraph->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignLeft | Qt::AlignBottom); // RA Curve driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(0)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(0)->setName("RA"); driftGraph->graph(0)->setLineStyle(QCPGraph::lsStepLeft); // DE Curve driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(1)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(1)->setName("DE"); driftGraph->graph(1)->setLineStyle(QCPGraph::lsStepLeft); // RA highlighted Point driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(2)->setLineStyle(QCPGraph::lsNone); driftGraph->graph(2)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10)); // DE highlighted Point driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(3)->setLineStyle(QCPGraph::lsNone); driftGraph->graph(3)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10)); // RA Pulse driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis2); QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); raPulseColor.setAlpha(75); driftGraph->graph(4)->setPen(QPen(raPulseColor)); driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); driftGraph->graph(4)->setName("RA Pulse"); driftGraph->graph(4)->setLineStyle(QCPGraph::lsStepLeft); // DEC Pulse driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis2); QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); dePulseColor.setAlpha(75); driftGraph->graph(5)->setPen(QPen(dePulseColor)); driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); driftGraph->graph(5)->setName("DEC Pulse"); driftGraph->graph(5)->setLineStyle(QCPGraph::lsStepLeft); //This will prevent the highlighted points and Pulses from showing up in the legend. driftGraph->legend->removeItem(5); driftGraph->legend->removeItem(4); driftGraph->legend->removeItem(3); driftGraph->legend->removeItem(2); //Dragging and zooming settings // make bottom axis transfer its range to the top axis if the graph gets zoomed: connect(driftGraph->xAxis, static_cast(&QCPAxis::rangeChanged), driftGraph->xAxis2, static_cast(&QCPAxis::setRange)); // update the second vertical axis properly if the graph gets zoomed. connect(driftGraph->yAxis, static_cast(&QCPAxis::rangeChanged), this, &Ekos::Guide::setCorrectionGraphScale); driftGraph->setInteractions(QCP::iRangeZoom); driftGraph->setInteraction(QCP::iRangeDrag, true); connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); //This sets the visibility of graph components to the stored values. driftGraph->graph(0)->setVisible(Options::rADisplayedOnGuideGraph()); //RA data driftGraph->graph(1)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC data driftGraph->graph(2)->setVisible(Options::rADisplayedOnGuideGraph()); //RA highlighted point driftGraph->graph(3)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC highlighted point driftGraph->graph(4)->setVisible(Options::rACorrDisplayedOnGuideGraph()); //RA Pulses driftGraph->graph(5)->setVisible(Options::dECorrDisplayedOnGuideGraph()); //DEC Pulses updateCorrectionsScaleVisibility(); } void Guide::initDriftPlot() { //drift plot double accuracyRadius = 2; driftPlot->setBackground(QBrush(Qt::black)); driftPlot->setSelectionTolerance(10); driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); driftPlot->xAxis->setTickPen(QPen(Qt::white, 1)); driftPlot->yAxis->setTickPen(QPen(Qt::white, 1)); driftPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); driftPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); driftPlot->xAxis->setTickLabelColor(Qt::white); driftPlot->yAxis->setTickLabelColor(Qt::white); driftPlot->xAxis->setLabelColor(Qt::white); driftPlot->yAxis->setLabelColor(Qt::white); driftPlot->xAxis->setLabelFont(QFont(font().family(), 10)); driftPlot->yAxis->setLabelFont(QFont(font().family(), 10)); driftPlot->xAxis->setTickLabelFont(QFont(font().family(), 9)); driftPlot->yAxis->setTickLabelFont(QFont(font().family(), 9)); driftPlot->xAxis->setLabelPadding(2); driftPlot->yAxis->setLabelPadding(2); driftPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray)); driftPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray)); driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->setInteractions(QCP::iRangeZoom); driftPlot->setInteraction(QCP::iRangeDrag, true); driftPlot->addGraph(); driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); driftPlot->addGraph(); driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); driftPlot->resize(190, 190); driftPlot->replot(); } void Guide::initCalibrationPlot() { calibrationPlot->setBackground(QBrush(Qt::black)); calibrationPlot->setSelectionTolerance(10); calibrationPlot->xAxis->setBasePen(QPen(Qt::white, 1)); calibrationPlot->yAxis->setBasePen(QPen(Qt::white, 1)); calibrationPlot->xAxis->setTickPen(QPen(Qt::white, 1)); calibrationPlot->yAxis->setTickPen(QPen(Qt::white, 1)); calibrationPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); calibrationPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); calibrationPlot->xAxis->setTickLabelColor(Qt::white); calibrationPlot->yAxis->setTickLabelColor(Qt::white); calibrationPlot->xAxis->setLabelColor(Qt::white); calibrationPlot->yAxis->setLabelColor(Qt::white); calibrationPlot->xAxis->setLabelFont(QFont(font().family(), 10)); calibrationPlot->yAxis->setLabelFont(QFont(font().family(), 10)); calibrationPlot->xAxis->setTickLabelFont(QFont(font().family(), 9)); calibrationPlot->yAxis->setTickLabelFont(QFont(font().family(), 9)); calibrationPlot->xAxis->setLabelPadding(2); calibrationPlot->yAxis->setLabelPadding(2); calibrationPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); calibrationPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); calibrationPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); calibrationPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); calibrationPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray)); calibrationPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray)); calibrationPlot->xAxis->setLabel(i18n("x (pixels)")); calibrationPlot->yAxis->setLabel(i18n("y (pixels)")); calibrationPlot->xAxis->setRange(-10, 10); calibrationPlot->yAxis->setRange(-10, 10); calibrationPlot->setInteractions(QCP::iRangeZoom); calibrationPlot->setInteraction(QCP::iRangeDrag, true); calibrationPlot->addGraph(); calibrationPlot->graph(0)->setLineStyle(QCPGraph::lsNone); calibrationPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 6)); calibrationPlot->graph(0)->setName("RA out"); calibrationPlot->addGraph(); calibrationPlot->graph(1)->setLineStyle(QCPGraph::lsNone); calibrationPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::white, 2), QBrush(), 4)); calibrationPlot->graph(1)->setName("RA in"); calibrationPlot->addGraph(); calibrationPlot->graph(2)->setLineStyle(QCPGraph::lsNone); calibrationPlot->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 6)); calibrationPlot->graph(2)->setName("DEC out"); calibrationPlot->addGraph(); calibrationPlot->graph(3)->setLineStyle(QCPGraph::lsNone); calibrationPlot->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::yellow, 2), QBrush(), 4)); calibrationPlot->graph(3)->setName("DEC in"); calLabel = new QCPItemText(calibrationPlot); calLabel->setColor(QColor(255,255,255)); calLabel->setPositionAlignment(Qt::AlignTop|Qt::AlignHCenter); calLabel->position->setType(QCPItemPosition::ptAxisRectRatio); calLabel->position->setCoords(0.5, 0); calLabel->setText(""); calLabel->setFont(QFont(font().family(), 10)); calLabel->setVisible(true); calibrationPlot->resize(190, 190); calibrationPlot->replot(); } void Guide::initView() { guideView = new FITSView(guideWidget, FITS_GUIDE); guideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); guideView->setBaseSize(guideWidget->size()); guideView->createFloatingToolBar(); QVBoxLayout *vlayout = new QVBoxLayout(); vlayout->addWidget(guideView); guideWidget->setLayout(vlayout); connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar); } void Guide::initConnections() { // Exposure Timeout captureTimeout.setSingleShot(true); connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout); // Guiding Box Size connect(boxSizeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTrackingBoxSize); // Guider CCD Selection connect(guiderCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::setDefaultCCD); connect(guiderCombo, static_cast(&QComboBox::activated), this, [&](int index) { if (guiderType == GUIDE_INTERNAL) { starCenter = QVector3D(); checkCCD(index); } } ); FOVScopeCombo->setCurrentIndex(Options::guideScopeType()); connect(FOVScopeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTelescopeType); // Dark Frame Check connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); // Subframe check if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); // ST4 Selection connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString & text) { setDefaultST4(text); setST4(text); }); // Binning Combo Selection connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateCCDBin); // RA/DEC Enable directions connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); // N/W and W/E direction enable connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(westControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(eastControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); // Auto star check connect(autoStarCheck, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); // Declination Swap connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); // PID Control - Proportional Gain connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // PID Control - Integral Gain connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // PID Control - Derivative Gain connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Max Pulse Duration (ms) connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Min Pulse Duration (ms) connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Capture connect(captureB, &QPushButton::clicked, this, [this]() { state = GUIDE_CAPTURE; emit newStatus(state); if(guiderType == GUIDE_PHD2) { configurePHD2Camera(); if(phd2Guider->isCurrentCameraNotInEkos()) appendLogText( i18n("The PHD2 camera is not available to Ekos, so you cannot see the captured images. But you will still see the Guide Star Image when you guide.")); else if(Options::guideSubframeEnabled()) { appendLogText( i18n("To receive PHD2 images other than the Guide Star Image, SubFrame must be unchecked. Unchecking it now to enable your image captures. You can re-enable it before Guiding")); subFrameCheck->setChecked(false); } phd2Guider->captureSingleFrame(); } else capture(); }); connect(loopB, &QPushButton::clicked, this, [this]() { state = GUIDE_LOOPING; emit newStatus(state); if(guiderType == GUIDE_PHD2) { configurePHD2Camera(); if(phd2Guider->isCurrentCameraNotInEkos()) appendLogText( i18n("The PHD2 camera is not available to Ekos, so you cannot see the captured images. But you will still see the Guide Star Image when you guide.")); else if(Options::guideSubframeEnabled()) { appendLogText( i18n("To receive PHD2 images other than the Guide Star Image, SubFrame must be unchecked. Unchecking it now to enable your image captures. You can re-enable it before Guiding")); subFrameCheck->setChecked(false); } phd2Guider->loop(); stopB->setEnabled(true); } else capture(); }); // Stop connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); // Clear Calibrate //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); // Guide connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); // Connect External Guide connect(externalConnectB, &QPushButton::clicked, this, [&]() { //setExternalGuiderBLOBEnabled(false); guider->Connect(); }); connect(externalDisconnectB, &QPushButton::clicked, this, [&]() { //setExternalGuiderBLOBEnabled(true); guider->Disconnect(); }); // Pulse Timer pulseTimer.setSingleShot(true); connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); //This connects all the buttons and slider below the guide plots. connect(accuracyRadiusSpin, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::buildTarget); connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory); connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint); connect(showRAPlotCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleShowRAPlot); connect(showDECPlotCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleShowDEPlot); connect(showRACorrectionsCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleRACorrectionsPlot); connect(showDECorrectionsCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleDECorrectionsPlot); connect(correctionSlider, &QSlider::sliderMoved, this, &Ekos::Guide::setCorrectionGraphScale); connect(showGuideRateToolTipB, &QPushButton::clicked, [this]() { QToolTip::showText(showGuideRateToolTipB->mapToGlobal(QPoint(10, 10)), showGuideRateToolTipB->toolTip(), showGuideRateToolTipB); }); connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither); // Guiding Rate - Advisory only onInfoRateChanged(spinBox_GuideRate->value()); connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); } void Guide::removeDevice(ISD::GDInterface *device) { device->disconnect(this); if (currentTelescope && (currentTelescope->getDeviceName() == device->getDeviceName())) { currentTelescope = nullptr; } else if (CCDs.contains(static_cast(device))) { CCDs.removeAll(static_cast(device)); guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName())); guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName() + QString(" Guider"))); if (CCDs.empty()) { currentCCD = nullptr; guiderCombo->setCurrentIndex(-1); } else guiderCombo->setCurrentIndex(0); checkCCD(); } auto st4 = std::find_if(ST4List.begin(), ST4List.end(), [device](ISD::ST4 * st) { return (st->getDeviceName() == device->getDeviceName()); }); if (st4 != ST4List.end()) { ST4List.removeOne(*st4); if (AODriver && (device->getDeviceName() == AODriver->getDeviceName())) AODriver = nullptr; ST4Combo->removeItem(ST4Combo->findText(device->getDeviceName())); if (ST4List.empty()) { ST4Driver = GuideDriver = nullptr; } else { setST4(ST4Combo->currentText()); } } } } diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h index 7b594217e..87d4d70bb 100644 --- a/kstars/ekos/guide/guide.h +++ b/kstars/ekos/guide/guide.h @@ -1,655 +1,657 @@ /* Ekos guide tool Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "ui_guide.h" #include "guideinterface.h" #include "ekos/ekos.h" #include "indi/indiccd.h" #include "indi/inditelescope.h" #include #include #include class QProgressIndicator; class QTabWidget; class FITSView; class FITSViewer; class ScrollGraph; namespace Ekos { class OpsCalibration; class OpsGuide; class InternalGuider; class PHD2; class LinGuider; /** * @class Guide * @short Performs calibration and autoguiding using an ST4 port or directly via the INDI driver. Can be used with the following external guiding applications: * PHD2 * LinGuider * * @author Jasem Mutlaq * @version 1.4 */ class Guide : public QWidget, public Ui::Guide { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Guide") Q_PROPERTY(Ekos::GuideState status READ status NOTIFY newStatus) Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) Q_PROPERTY(QString camera READ camera WRITE setCamera) Q_PROPERTY(QString st4 READ st4 WRITE setST4) Q_PROPERTY(double exposure READ exposure WRITE setExposure) Q_PROPERTY(QList axisDelta READ axisDelta NOTIFY newAxisDelta) Q_PROPERTY(QList axisSigma READ axisSigma NOTIFY newAxisSigma) public: Guide(); ~Guide(); enum GuiderStage { CALIBRATION_STAGE, GUIDE_STAGE }; enum GuiderType { GUIDE_INTERNAL, GUIDE_PHD2, GUIDE_LINGUIDER }; /** @defgroup GuideDBusInterface Ekos DBus Interface - Capture Module * Ekos::Guide interface provides advanced scripting capabilities to calibrate and guide a mount via a CCD camera. */ /*@{*/ /** DBUS interface function. * select the CCD device from the available CCD drivers. * @param device The CCD device name * @return Returns true if CCD device is found and set, false otherwise. */ Q_SCRIPTABLE bool setCamera(const QString &device); Q_SCRIPTABLE QString camera(); /** DBUS interface function. * select the ST4 device from the available ST4 drivers. * @param device The ST4 device name * @return Returns true if ST4 device is found and set, false otherwise. */ Q_SCRIPTABLE bool setST4(const QString &device); Q_SCRIPTABLE QString st4(); /** DBUS interface function. * @return Returns List of registered ST4 devices. */ Q_SCRIPTABLE QStringList getST4Devices(); /** DBUS interface function. * @brief connectGuider Establish connection to guider application. For internal guider, this always returns true. * @return True if successfully connected, false otherwise. */ Q_SCRIPTABLE bool connectGuider(); /** DBUS interface function. * @brief disconnectGuider Disconnect from guider application. For internal guider, this always returns true. * @return True if successfully disconnected, false otherwise. */ Q_SCRIPTABLE bool disconnectGuider(); /** * @brief getStatus Return guide module status * @return state of guide module from Ekos::GuideState */ Q_SCRIPTABLE Ekos::GuideState status() { return state; } /** DBUS interface function. * Set CCD exposure value * @param value exposure value in seconds. */ Q_SCRIPTABLE Q_NOREPLY void setExposure(double value); double exposure() { return exposureIN->value(); } /** DBUS interface function. * Set image filter to apply to the image after capture. * @param value Image filter (Auto Stretch, High Contrast, Equalize, High Pass) */ Q_SCRIPTABLE Q_NOREPLY void setImageFilter(const QString &value); /** DBUS interface function. * Set calibration Use Two Axis option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, calibration will be performed in both RA and DEC axis. Otherwise, only RA axis will be calibrated. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationTwoAxis(bool enable); /** DBUS interface function. * Set auto star calibration option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will attempt to automatically select the best guide star and proceed with the calibration procedure. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoStar(bool enable); /** DBUS interface function. * In case of automatic star selection, calculate the appropriate square size given the selected star width. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will attempt to automatically select the best square size for calibration and guiding phases. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoSquareSize(bool enable); /** DBUS interface function. * Set calibration dark frame option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, a dark frame will be captured to subtract from the light frame. */ Q_SCRIPTABLE Q_NOREPLY void setDarkFrameEnabled(bool enable); /** DBUS interface function. * Set calibration parameters. * @param pulseDuration Pulse duration in milliseconds to use in the calibration steps. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationPulseDuration(int pulseDuration); /** DBUS interface function. * Set guiding box size. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param index box size index (0 to 4) for box size from 8 to 128 pixels. The box size should be suitable for the size of the guide star selected. The boxSize is also used to select the subframe size around the guide star. Default is 16 pixels */ Q_SCRIPTABLE Q_NOREPLY void setGuideBoxSizeIndex(int index); /** DBUS interface function. * Set guiding algorithm. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param index Select the algorithm used to calculate the centroid of the guide star (0 --> Smart, 1 --> Fast, 2 --> Auto, 3 --> No thresh). */ Q_SCRIPTABLE Q_NOREPLY void setGuideAlgorithmIndex(int index); /** DBUS interface function. * Set rapid guiding option. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, it will activate RapidGuide in the CCD driver. When Rapid Guide is used, no frames are sent to Ekos for analysis and the centeroid calculations are done in the CCD driver. */ //Q_SCRIPTABLE Q_NOREPLY void setGuideRapidEnabled(bool enable); /** DBUS interface function. * Enable or disables dithering. Set dither range * @param enable if true, dithering is enabled and is performed after each exposure is complete. Otherwise, dithering is disabled. * @param value dithering range in pixels. Ekos will move the guide star in a random direction for the specified dithering value in pixels. */ Q_SCRIPTABLE Q_NOREPLY void setDitherSettings(bool enable, double value); /** @}*/ void addCamera(ISD::GDInterface *newCCD); void configurePHD2Camera(); void setTelescope(ISD::GDInterface *newTelescope); void addST4(ISD::ST4 *setST4); void setAO(ISD::ST4 *newAO); void removeDevice(ISD::GDInterface *device); bool isDithering(); void addGuideHead(ISD::GDInterface *newCCD); void syncTelescopeInfo(); void syncCCDInfo(); /** * @brief clearLog As the name suggests */ void clearLog(); QStringList logText() { return m_LogText; } /** * @return Return current log text of guide module */ QString getLogText() { return m_LogText.join("\n"); } /** * @brief getStarPosition Return star center as selected by the user or auto-detected by KStars * @return QVector3D of starCenter. The 3rd parameter is used to store current bin settings and in unrelated to the star position. */ QVector3D getStarPosition() { return starCenter; } // Tracking Box int getTrackingBoxSize() { return boxSizeCombo->currentText().toInt(); } //void startRapidGuide(); //void stopRapidGuide(); GuideInterface *getGuider() { return guider; } public slots: /** DBUS interface function. * Start the autoguiding operation. * @return Returns true if guiding started successfully, false otherwise. */ Q_SCRIPTABLE bool guide(); /** DBUS interface function. * Stop any active calibration, guiding, or dithering operation * @return Returns true if operation is stopped successfully, false otherwise. */ Q_SCRIPTABLE bool abort(); /** DBUS interface function. * Start the calibration operation. Note that this will not start guiding automatically. * @return Returns true if calibration started successfully, false otherwise. */ Q_SCRIPTABLE bool calibrate(); /** DBUS interface function. * Clear calibration data. Next time any guide operation is performed, a calibration is first started. */ Q_SCRIPTABLE Q_NOREPLY void clearCalibration(); /** DBUS interface function. * @brief dither Starts dithering process in a random direction restricted by the number of pixels specified in dither options * @return True if dither started successfully, false otherwise. */ Q_SCRIPTABLE bool dither(); /** DBUS interface function. * @brief suspend Suspend autoguiding * @return True if successful, false otherwise. */ Q_SCRIPTABLE bool suspend(); /** DBUS interface function. * @brief resume Resume autoguiding * @return True if successful, false otherwise. */ Q_SCRIPTABLE bool resume(); /** DBUS interface function. * Capture a guide frame * @return Returns true if capture command is sent successfully to INDI server. */ Q_SCRIPTABLE bool capture(); /** DBUS interface function. * Set guiding options. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, it will select a subframe around the guide star depending on the boxSize size. */ Q_SCRIPTABLE Q_NOREPLY void setSubFrameEnabled(bool enable); /** DBUS interface function. * Selects which guiding process to utilize for calibration & guiding. * @param type Type of guider process to use. 0 for internal guider, 1 for external PHD2, 2 for external lin_guider. Pass -1 to select default guider in options. * @return True if guiding is switched to the new requested type. False otherwise. */ Q_SCRIPTABLE bool setGuiderType(int type); /** DBUS interface function. * @brief axisDelta returns the last immediate axis delta deviation in arcseconds. This is the deviation of locked star position when guiding started. * @return List of doubles. First member is RA deviation. Second member is DE deviation. */ Q_SCRIPTABLE QList axisDelta(); /** DBUS interface function. * @brief axisSigma return axis sigma deviation in arcseconds RMS. This is the RMS deviation of locked star position when guiding started. * @return List of doubles. First member is RA deviation. Second member is DE deviation. */ Q_SCRIPTABLE QList axisSigma(); /** * @brief checkCCD Check all CCD parameters and ensure all variables are updated to reflect the selected CCD * @param ccdNum CCD index number in the CCD selection combo box */ void checkCCD(int ccdNum = -1); /** * @brief checkExposureValue This function is called by the INDI framework whenever there is a new exposure value. We use it to know if there is a problem with the exposure * @param targetChip Chip for which the exposure is undergoing * @param exposure numbers of seconds left in the exposure * @param expState State of the exposure property */ void checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState); /** * @brief newFITS is called by the INDI framework whenever there is a new BLOB arriving */ void newFITS(IBLOB *); /** * @brief setST4 Sets a new ST4 device from the combobox index * @param index Index of selected ST4 in the combobox */ void setST4(int index); /* * @brief processRapidStarData is called by INDI framework when we receive new Rapid Guide data * @param targetChip target Chip for which rapid guide is enabled * @param dx Deviation in X * @param dy Deviation in Y * @param fit fitting score */ //void processRapidStarData(ISD::CCDChip *targetChip, double dx, double dy, double fit); /** * @brief Set telescope and guide scope info. All measurements is in millimeters. * @param primaryFocalLength Primary Telescope Focal Length. Set to 0 to skip setting this value. * @param primaryAperture Primary Telescope Aperture. Set to 0 to skip setting this value. * @param guideFocalLength Guide Telescope Focal Length. Set to 0 to skip setting this value. * @param guideAperture Guide Telescope Aperture. Set to 0 to skip setting this value. */ void setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture); // This Function will allow PHD2 to update the exposure values to the recommended ones. QString setRecommendedExposureValues(QList values); // Append Log entry void appendLogText(const QString &); // Update Guide module status void setStatus(Ekos::GuideState newState); // Update Capture Module status void setCaptureStatus(Ekos::CaptureState newState); // Update Mount module status void setMountStatus(ISD::Telescope::Status newState); + void setMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt); + // Update Pier Side void setPierSide(ISD::Telescope::PierSide newSide); // Star Position void setStarPosition(const QVector3D &newCenter, bool updateNow); // Capture void setCaptureComplete(); // Send pulse to ST4 driver bool sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs); bool sendPulse(GuideDirection dir, int msecs); /** * @brief setDECSwap Change ST4 declination pulse direction. +DEC pulses increase DEC if swap is OFF. When on +DEC pulses result in decreasing DEC. * @param enable True to enable DEC swap. Off to disable it. */ void setDECSwap(bool enable); void refreshColorScheme(); void setupNSEWLabels(); //plot slots void handleVerticalPlotSizeChange(); void handleHorizontalPlotSizeChange(); void clearGuideGraphs(); void clearCalibrationGraphs(); void slotAutoScaleGraphs(); void buildTarget(); void guideHistory(); void setLatestGuidePoint(bool isChecked); void toggleShowRAPlot(bool isChecked); void toggleShowDEPlot(bool isChecked); void toggleRACorrectionsPlot(bool isChecked); void toggleDECorrectionsPlot(bool isChecked); void exportGuideData(); void setCorrectionGraphScale(); void updateCorrectionsScaleVisibility(); void updateDirectionsFromPHD2(QString mode); void guideAfterMeridianFlip(); protected slots: void updateTelescopeType(int index); void updateCCDBin(int index); /** * @brief processCCDNumber Process number properties arriving from CCD. Currently, binning changes are processed. * @param nvp pointer to number property. */ void processCCDNumber(INumberVectorProperty *nvp); /** * @brief setTrackingStar Gets called when the user select a star in the guide frame * @param x X coordinate of star * @param y Y coordinate of star */ void setTrackingStar(int x, int y); void saveDefaultGuideExposure(); void updateTrackingBoxSize(int currentIndex); // Display guide information when hovering over the drift graph void driftMouseOverLine(QMouseEvent *event); // Reset graph if right clicked void driftMouseClicked(QMouseEvent *event); //void onXscaleChanged( int i ); //void onYscaleChanged( int i ); void onThresholdChanged(int i); void onInfoRateChanged(double val); void onEnableDirRA(bool enable); void onEnableDirDEC(bool enable); void syncSettings(); //void onRapidGuideChanged(bool enable); void setAxisDelta(double ra, double de); void setAxisSigma(double ra, double de); void setAxisPulse(double ra, double de); void calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString& message = QString(""), double dx = 0, double dy = 0); void processGuideOptions(); void onControlDirectionChanged(bool enable); void updatePHD2Directions(); void showFITSViewer(); void processCaptureTimeout(); void ditherDirectly(); signals: void newLog(const QString &text); void newStatus(Ekos::GuideState status); void newStarPixmap(QPixmap &); void newProfilePixmap(QPixmap &); // Immediate deviations in arcsecs void newAxisDelta(double ra, double de); // Sigma deviations in arcsecs RMS void newAxisSigma(double ra, double de); void guideChipUpdated(ISD::CCDChip *); void driverTimedout(const QString &deviceName); private slots: void setDefaultST4(const QString &driver); void setDefaultCCD(const QString &ccd); private: void resizeEvent(QResizeEvent *event) override; /** * @brief updateGuideParams Update the guider and frame parameters due to any changes in the mount and/or ccd frame */ void updateGuideParams(); /** * @brief syncTrackingBoxPosition Sync the tracking box to the current selected star center */ void syncTrackingBoxPosition(); /** * @brief loadSettings Loads and applies all settings from KStars options */ void loadSettings(); /** * @brief saveSettings Saves all current settings into KStars options */ void saveSettings(); /** * @brief setBusy Indicate busy status within the module visually * @param enable True if module is busy, false otherwise */ void setBusy(bool enable); /** * @brief setBLOBEnabled Enable or disable BLOB reception from current CCD if using external guider * @param enable True to enable BLOB reception, false to disable BLOB reception * @param name CCD to enable to disable. If empty (default), then action is applied to all CCDs. */ void setExternalGuiderBLOBEnabled(bool enable); void handleManualDither(); // Operation stack void buildOperationStack(GuideState operation); bool executeOperationStack(); bool executeOneOperation(GuideState operation); // Init Functions void initPlots(); void initDriftGraph(); void initDriftPlot(); void initCalibrationPlot(); void initView(); void initConnections(); bool captureOneFrame(); // Driver void reconnectDriver(const QString &camera, const QString &via); // Operation Stack QStack operationStack; // Devices ISD::CCD *currentCCD { nullptr }; QString lastPHD2CameraName; //This is for the configure PHD2 camera method. ISD::Telescope *currentTelescope { nullptr }; ISD::ST4 *ST4Driver { nullptr }; ISD::ST4 *AODriver { nullptr }; ISD::ST4 *GuideDriver { nullptr }; // Device Containers QList ST4List; QList CCDs; // Guider process GuideInterface *guider { nullptr }; GuiderType guiderType { GUIDE_INTERNAL }; // Star QVector3D starCenter; // Guide Params double ccdPixelSizeX { -1 }; double ccdPixelSizeY { -1 }; double aperture { -1 }; double focal_length { -1 }; double guideDeviationRA { 0 }; double guideDeviationDEC { 0 }; double pixScaleX { -1 }; double pixScaleY { -1 }; // Rapid Guide //bool rapidGuideReticleSet; // State GuideState state { GUIDE_IDLE }; // Guide timer QTime guideTimer; // Capture timeout timer QTimer captureTimeout; uint8_t m_CaptureTimeoutCounter { 0 }; uint8_t m_DeviceRestartCounter { 0 }; // Pulse Timer QTimer pulseTimer; // Log QStringList m_LogText; // Misc bool useGuideHead { false }; // Progress Activity Indicator QProgressIndicator *pi { nullptr }; // Options OpsCalibration *opsCalibration { nullptr }; OpsGuide *opsGuide { nullptr }; // Guide Frame FITSView *guideView { nullptr }; // Calibration done already? bool calibrationComplete { false }; // Was the modified frame subFramed? bool subFramed { false }; // CCD Chip frame settings QMap frameSettings; // Profile Pixmap QPixmap profilePixmap; // Flag to start auto calibration followed immediately by guiding //bool autoCalibrateGuide { false }; // Pointers of guider processes QPointer internalGuider; QPointer phd2Guider; QPointer linGuider; QPointer fv; double primaryFL = -1, primaryAperture = -1, guideFL = -1, guideAperture = -1; ISD::Telescope::Status m_MountStatus { ISD::Telescope::MOUNT_IDLE }; QCPCurve *centralTarget { nullptr }; QCPCurve *yellowTarget { nullptr }; QCPCurve *redTarget { nullptr }; QCPCurve *concentricRings { nullptr }; bool graphOnLatestPt = true; QUrl guideURLPath; //This is for enforcing the PHD2 Star lock when Guide is pressed, //autostar is not selected, and the user has chosen a star. //This connection storage is so that the connection can be disconnected after enforcement QMetaObject::Connection guideConnect; QCPItemText *calLabel { nullptr }; }; } diff --git a/kstars/ekos/guide/guideinterface.cpp b/kstars/ekos/guide/guideinterface.cpp index dccac03ab..bfd96e370 100644 --- a/kstars/ekos/guide/guideinterface.cpp +++ b/kstars/ekos/guide/guideinterface.cpp @@ -1,73 +1,87 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "guideinterface.h" #include "guide.h" #define MAX_GUIDE_STARS 10 namespace Ekos { bool GuideInterface::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength) { this->ccdPixelSizeX = ccdPixelSizeX; this->ccdPixelSizeY = ccdPixelSizeY; this->mountAperture = mountAperture; this->mountFocalLength = mountFocalLength; return true; } bool GuideInterface::getGuiderParams(double *ccdPixelSizeX, double *ccdPixelSizeY, double *mountAperture, double *mountFocalLength) { *ccdPixelSizeX = this->ccdPixelSizeX; *ccdPixelSizeY = this->ccdPixelSizeY; *mountAperture = this->mountAperture; *mountFocalLength = this->mountFocalLength; return true; } bool GuideInterface::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY) { if (w <= 0 || h <= 0) return false; subX = x; subY = y; subW = w; subH = h; subBinX = binX; subBinY = binY; return true; } bool GuideInterface::getFrameParams(uint16_t *x, uint16_t *y, uint16_t *w, uint16_t *h, uint16_t *binX, uint16_t *binY) { *x = subX; *y = subY; *w = subW; *h = subH; *binX = subBinX; *binY = subBinY; return true; } void GuideInterface::setStarPosition(QVector3D& starCenter) { INDI_UNUSED(starCenter); } + +void GuideInterface::setMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt) +{ + mountRA = dms::fromString(ra, false); + mountDEC = dms::fromString(dec, true); + mountAzimuth = dms::fromString(az, true); + mountAltitude = dms::fromString(alt, true); +} + +void GuideInterface::setPierSide(ISD::Telescope::PierSide newSide) +{ + pierSide = newSide; +} + } diff --git a/kstars/ekos/guide/guideinterface.h b/kstars/ekos/guide/guideinterface.h index 031f92b45..92d43bbe3 100644 --- a/kstars/ekos/guide/guideinterface.h +++ b/kstars/ekos/guide/guideinterface.h @@ -1,96 +1,103 @@ /* Ekos guide class interface Copyright (C) 2016 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "ekos/ekos.h" - +#include "indi/inditelescope.h" #include #include #include class QString; namespace Ekos { /** * @class GuideInterface * @short Interface skeleton for implementation of different guiding applications and/or routines * * @author Jasem Mutlaq * @version 1.0 */ class GuideInterface : public QObject { Q_OBJECT public: GuideInterface() = default; virtual ~GuideInterface() override = default; virtual bool Connect() = 0; virtual bool Disconnect() = 0; virtual bool isConnected() = 0; virtual bool calibrate() = 0; virtual bool guide() = 0; virtual bool suspend() = 0; virtual bool resume() = 0; virtual bool abort() = 0; virtual bool dither(double pixels) = 0; virtual bool clearCalibration() = 0; virtual bool reacquire() { return false; } virtual bool setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength); virtual bool getGuiderParams(double *ccdPixelSizeX, double *ccdPixelSizeY, double *mountAperture, double *mountFocalLength); virtual bool setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY); virtual bool getFrameParams(uint16_t *x, uint16_t *y, uint16_t *w, uint16_t *h, uint16_t *binX, uint16_t *binY); virtual void setStarPosition(QVector3D& starCenter); + virtual void setMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt); + virtual void setPierSide(ISD::Telescope::PierSide newSide); + enum CalibrationUpdateType { RA_IN, RA_OUT, DEC_IN, DEC_OUT, CALIBRATION_MESSAGE_ONLY }; signals: void newLog(const QString &); void newStatus(Ekos::GuideState); void newAxisDelta(double delta_ra, double delta_dec); void newAxisSigma(double sigma_ra, double sigma_dec); void newAxisPulse(double pulse_ra, double pulse_dec); void newStarPosition(const QVector3D &newCenter, bool updateNow); void newStarPixmap(QPixmap &); void calibrationUpdate(CalibrationUpdateType type, const QString &message = QString(""), double x = 0, double y = 0); void frameCaptureRequested(); void guideEquipmentUpdated(); protected: Ekos::GuideState state { GUIDE_IDLE }; double ccdPixelSizeX { 0 }; double ccdPixelSizeY { 0 }; double mountAperture { 0 }; double mountFocalLength { 0 }; uint16_t subX { 0 }; uint16_t subY { 0 }; uint16_t subW { 0 }; uint16_t subH { 0 }; uint16_t subBinX { 1 }; uint16_t subBinY { 1 }; + + // Recent mount position. + dms mountRA, mountDEC, mountAzimuth, mountAltitude; + ISD::Telescope::PierSide pierSide { ISD::Telescope::PIER_UNKNOWN }; }; } diff --git a/kstars/ekos/guide/internalguide/gmath.cpp b/kstars/ekos/guide/internalguide/gmath.cpp index a98007599..c01639e98 100644 --- a/kstars/ekos/guide/internalguide/gmath.cpp +++ b/kstars/ekos/guide/internalguide/gmath.cpp @@ -1,1808 +1,1836 @@ /* Ekos guide tool Copyright (C) 2012 Andrew Stepanenko Modified by Jasem Mutlaq for KStars. This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "gmath.h" #include "imageautoguiding.h" #include "Options.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "auxiliary/kspaths.h" #include "ekos_guide_debug.h" #include #include #include #define DEF_SQR_0 (8 - 0) #define DEF_SQR_1 (16 - 0) #define DEF_SQR_2 (32 - 0) #define DEF_SQR_3 (64 - 0) #define DEF_SQR_4 (128 - 0) const guide_square_t guide_squares[] = { { DEF_SQR_0, DEF_SQR_0 *DEF_SQR_0 * 1.0 }, { DEF_SQR_1, DEF_SQR_1 *DEF_SQR_1 * 1.0 }, { DEF_SQR_2, DEF_SQR_2 *DEF_SQR_2 * 1.0 }, { DEF_SQR_3, DEF_SQR_3 *DEF_SQR_3 * 1.0 }, { DEF_SQR_4, DEF_SQR_4 *DEF_SQR_4 * 1.0 }, { -1, -1 } }; const square_alg_t guide_square_alg[] = { { SMART_THRESHOLD, "Smart" }, { SEP_THRESHOLD, "SEP" }, { CENTROID_THRESHOLD, "Fast" }, { AUTO_THRESHOLD, "Auto" }, { NO_THRESHOLD, "No thresh." }, { -1, { 0 } } }; struct Peak { int x; int y; float val; Peak() = default; Peak(int x_, int y_, float val_) : x(x_), y(y_), val(val_) { } bool operator<(const Peak &rhs) const { return val < rhs.val; } }; // JM: Why not use QPoint? typedef struct { int x, y; } point_t; cgmath::cgmath() : QObject() { // sys... ROT_Z = Ekos::Matrix(0); // sky coord. system vars. star_pos = Vector(0); scr_star_pos = Vector(0); reticle_pos = Vector(0); reticle_orts[0] = Vector(0); reticle_orts[1] = Vector(0); reticle_angle = 0; ditherRate[0] = ditherRate[1] = -1; // processing in_params.reset(); out_params.reset(); channel_ticks[GUIDE_RA] = channel_ticks[GUIDE_DEC] = 0; accum_ticks[GUIDE_RA] = accum_ticks[GUIDE_DEC] = 0; drift[GUIDE_RA] = new double[MAX_ACCUM_CNT]; drift[GUIDE_DEC] = new double[MAX_ACCUM_CNT]; memset(drift[GUIDE_RA], 0, sizeof(double) * MAX_ACCUM_CNT); memset(drift[GUIDE_DEC], 0, sizeof(double) * MAX_ACCUM_CNT); drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0; QString logFileName = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "guide_log.txt"; logFile.setFileName(logFileName); } cgmath::~cgmath() { delete[] drift[GUIDE_RA]; delete[] drift[GUIDE_DEC]; foreach (float *region, referenceRegions) delete[] region; referenceRegions.clear(); } bool cgmath::setVideoParameters(int vid_wd, int vid_ht, int binX, int binY) { if (vid_wd <= 0 || vid_ht <= 0) return false; video_width = vid_wd / binX; video_height = vid_ht / binY; subBinX = binX; subBinY = binY; //set_reticle_params( video_width/2, video_height/2, -1 ); // keep orientation return true; } void cgmath::setGuideView(FITSView *image) { guideView = image; /*if (guideView) { FITSData *image_data = guideView->getImageData(); setDataBuffer(image_data->getImageBuffer()); setVideoParameters(image_data->getWidth(), image_data->getHeight()); }*/ } bool cgmath::setGuiderParameters(double ccd_pix_wd, double ccd_pix_ht, double guider_aperture, double guider_focal) { if (ccd_pix_wd < 0) ccd_pix_wd = 0; if (ccd_pix_ht < 0) ccd_pix_ht = 0; if (guider_focal <= 0) guider_focal = 1; ccd_pixel_width = ccd_pix_wd / 1000.0; // from mkm to mm ccd_pixel_height = ccd_pix_ht / 1000.0; // from mkm to mm aperture = guider_aperture; focal = guider_focal; return true; } void cgmath::getGuiderParameters(double *ccd_pix_wd, double *ccd_pix_ht, double *guider_aperture, double *guider_focal) { *ccd_pix_wd = ccd_pixel_width * 1000.0; *ccd_pix_ht = ccd_pixel_height * 1000.0; *guider_aperture = aperture; *guider_focal = focal; } +// This logging will be removed in favor of guidelog.h. void cgmath::createGuideLog() { logFile.close(); logFile.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&logFile); out << "Guiding rate,x15 arcsec/sec: " << Options::guidingRate() << endl; out << "Focal,mm: " << focal << endl; out << "Aperture,mm: " << aperture << endl; out << "F/D: " << focal / aperture << endl; out << "Frame #, Time Elapsed (ms), RA Error (arcsec), RA Correction (ms), RA Correction Direction, DEC Error " "(arcsec), DEC Correction (ms), DEC Correction Direction" << endl; logTime.restart(); } bool cgmath::setReticleParameters(double x, double y, double ang) { // check frame ranges if (x < 0) x = 0; if (y < 0) y = 0; if (x >= (double)video_width - 1) x = (double)video_width - 1; if (y >= (double)video_height - 1) y = (double)video_height - 1; reticle_pos = Vector(x, y, 0); if (ang >= 0) reticle_angle = ang; ROT_Z = Ekos::RotateZ(-M_PI * reticle_angle / 180.0); // NOTE!!! sing '-' derotates star coordinate system reticle_orts[0] = Vector(1, 0, 0) * 100; reticle_orts[1] = Vector(0, 1, 0) * 100; reticle_orts[0] = reticle_orts[0] * ROT_Z; reticle_orts[1] = reticle_orts[1] * ROT_Z; return true; } bool cgmath::getReticleParameters(double *x, double *y, double *ang) const { *x = reticle_pos.x; *y = reticle_pos.y; if (ang) *ang = reticle_angle; return true; } int cgmath::getSquareAlgorithmIndex(void) const { return square_alg_idx; } info_params_t cgmath::getInfoParameters(void) const { info_params_t ret; Vector p; ret.aperture = aperture; ret.focal = focal; ret.focal_ratio = focal / aperture; p = Vector(video_width, video_height, 0); p = point2arcsec(p); p /= 60; // convert to minutes ret.fov_wd = p.x; ret.fov_ht = p.y; return ret; } uint32_t cgmath::getTicks(void) const { return ticks; } void cgmath::getStarDrift(double *dx, double *dy) const { *dx = star_pos.x; *dy = star_pos.y; } void cgmath::getStarScreenPosition(double *dx, double *dy) const { *dx = scr_star_pos.x; *dy = scr_star_pos.y; } bool cgmath::reset(void) { // square_alg_idx = AUTO_THRESHOLD; // // sky coord. system vars. // star_pos = Vector(0); // scr_star_pos = Vector(0); // setReticleParameters(video_width / 2, video_height / 2, 0.0); ticks = 0; channel_ticks[GUIDE_RA] = channel_ticks[GUIDE_DEC] = 0; accum_ticks[GUIDE_RA] = accum_ticks[GUIDE_DEC] = 0; drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0; out_params.reset(); memset(drift[GUIDE_RA], 0, sizeof(double) * MAX_ACCUM_CNT); memset(drift[GUIDE_DEC], 0, sizeof(double) * MAX_ACCUM_CNT); // cleanup stat vars. sum = 0; return true; } /*void cgmath::move_square( double newx, double newy ) { square_pos.x = newx; square_pos.y = newy; // check frame ranges if (lastBinX == subBinX) { if( square_pos.x < 0 ) square_pos.x = 0; if( square_pos.y < 0 ) square_pos.y = 0; if( square_pos.x+(double)square_size > (double)video_width ) square_pos.x = (double)(video_width - square_size); if( square_pos.y+(double)square_size > (double)video_height ) square_pos.y = (double)(video_height - square_size); } // FITS Image takes center coords if (guide_frame) { guide_frame->setTrackingBoxEnabled(true); //guide_frame->setTrackingBoxCenter(QPointF(square_pos.x+square_size/2, square_pos.y+square_size/2)); guide_frame->setTrackingBox(QRect(square_pos.x, square_pos.y, square_size/subBinX, square_size/subBinY)); } } void cgmath::resize_square( int size_idx ) { if( size_idx < 0 || size_idx >= (int)(sizeof(guide_squares)/sizeof(guide_square_t))-1) return; if (square_size != guide_squares[size_idx].size) { square_pos.x += (square_size-guide_squares[size_idx].size)/2; square_pos.y += (square_size-guide_squares[size_idx].size)/2; } square_size = guide_squares[size_idx].size; square_square = guide_squares[size_idx].square; square_idx = size_idx; // check position if (guide_frame) { guide_frame->setTrackingBoxEnabled(true); //guide_frame->setTrackingBoxSize(QSize(square_size,square_size)); guide_frame->setTrackingBox(QRect(square_pos.x/subBinX, square_pos.y/subBinY, square_size/subBinX, square_size/subBinY)); } }*/ void cgmath::setSquareAlgorithm(int alg_idx) { if (alg_idx < 0 || alg_idx >= (int)(sizeof(guide_square_alg) / sizeof(square_alg_t)) - 1) return; square_alg_idx = alg_idx; in_params.threshold_alg_idx = square_alg_idx; } Vector cgmath::point2arcsec(const Vector &p) const { Vector arcs; // arcs = 3600*180/pi * (pix*ccd_pix_sz) / focal_len arcs.x = 206264.8062470963552 * p.x * subBinX * ccd_pixel_width / focal; arcs.y = 206264.8062470963552 * p.y * subBinY * ccd_pixel_height / focal; return arcs; } bool cgmath::calculateAndSetReticle1D(double start_x, double start_y, double end_x, double end_y, int RATotalPulse) { double phi; phi = calculatePhi(start_x, start_y, end_x, end_y); if (phi < 0) return false; setReticleParameters(start_x, start_y, phi); if (RATotalPulse > 0) { double x = end_x - start_x; double y = end_y - start_y; double len = sqrt(x * x + y * y); // Total pulse includes start --> end --> start ditherRate[GUIDE_RA] = RATotalPulse / (2 * len); qCDebug(KSTARS_EKOS_GUIDE) << "Dither RA Rate " << ditherRate[GUIDE_RA] << " ms/Pixel"; } return true; } bool cgmath::calculateAndSetReticle2D(double start_ra_x, double start_ra_y, double end_ra_x, double end_ra_y, double start_dec_x, double start_dec_y, double end_dec_x, double end_dec_y, bool *swap_dec, int RATotalPulse, int DETotalPulse) { double phi_ra = 0; // angle calculated by GUIDE_RA drift double phi_dec = 0; // angle calculated by GUIDE_DEC drift double phi = 0; Vector ra_vect = Normalize(Vector(end_ra_x - start_ra_x, -(end_ra_y - start_ra_y), 0)); Vector dec_vect = Normalize(Vector(end_dec_x - start_dec_x, -(end_dec_y - start_dec_y), 0)); Vector try_increase = dec_vect * Ekos::RotateZ(M_PI / 2); Vector try_decrease = dec_vect * Ekos::RotateZ(-M_PI / 2); double cos_increase = try_increase & ra_vect; double cos_decrease = try_decrease & ra_vect; bool do_increase = cos_increase > cos_decrease ? true : false; phi_ra = calculatePhi(start_ra_x, start_ra_y, end_ra_x, end_ra_y); if (phi_ra < 0) return false; phi_dec = calculatePhi(start_dec_x, start_dec_y, end_dec_x, end_dec_y); if (phi_dec < 0) return false; if (do_increase) phi_dec += 90; else phi_dec -= 90; if (phi_dec > 360) phi_dec -= 360.0; if (phi_dec < 0) phi_dec += 360.0; if (fabs(phi_dec - phi_ra) > 180) { if (phi_ra > phi_dec) phi_ra -= 360; else phi_dec -= 360; } // average angles phi = (phi_ra + phi_dec) / 2; if (phi < 0) phi += 360.0; // check DEC if (swap_dec) *swap_dec = dec_swap = do_increase ? false : true; setReticleParameters(start_ra_x, start_ra_y, phi); if (RATotalPulse > 0) { double x = end_ra_x - start_ra_x; double y = end_ra_y - start_ra_y; double len = sqrt(x * x + y * y); ditherRate[GUIDE_RA] = RATotalPulse / (2 * len); qCDebug(KSTARS_EKOS_GUIDE) << "Dither RA Rate " << ditherRate[GUIDE_RA] << " ms/Pixel"; } if (DETotalPulse > 0) { double x = end_dec_x - start_dec_x; double y = end_dec_y - start_dec_y; double len = sqrt(x * x + y * y); ditherRate[GUIDE_DEC] = DETotalPulse / (2 * len); qCDebug(KSTARS_EKOS_GUIDE) << "Dither DEC Rate " << ditherRate[GUIDE_DEC] << " ms/Pixel"; } return true; } double cgmath::calculatePhi(double start_x, double start_y, double end_x, double end_y) const { double delta_x, delta_y; double phi; delta_x = end_x - start_x; delta_y = -(end_y - start_y); //if( (!Vector(delta_x, delta_y, 0)) < 2.5 ) // JM 2015-12-10: Lower threshold to 1 pixel if ((!Vector(delta_x, delta_y, 0)) < 1) return -1; // 90 or 270 degrees if (fabs(delta_x) < fabs(delta_y) / 1000000.0) { phi = delta_y > 0 ? 90.0 : 270; } else { phi = 180.0 / M_PI * atan2(delta_y, delta_x); if (phi < 0) phi += 360.0; } return phi; } void cgmath::do_ticks(void) { ticks++; channel_ticks[GUIDE_RA]++; channel_ticks[GUIDE_DEC]++; if (channel_ticks[GUIDE_RA] >= MAX_ACCUM_CNT) channel_ticks[GUIDE_RA] = 0; if (channel_ticks[GUIDE_DEC] >= MAX_ACCUM_CNT) channel_ticks[GUIDE_DEC] = 0; accum_ticks[GUIDE_RA]++; accum_ticks[GUIDE_DEC]++; if (accum_ticks[GUIDE_RA] >= in_params.accum_frame_cnt[GUIDE_RA]) accum_ticks[GUIDE_RA] = 0; if (accum_ticks[GUIDE_DEC] >= in_params.accum_frame_cnt[GUIDE_DEC]) accum_ticks[GUIDE_DEC] = 0; } //-------------------- Processing --------------------------- void cgmath::start(void) { ticks = 0; channel_ticks[GUIDE_RA] = channel_ticks[GUIDE_DEC] = 0; accum_ticks[GUIDE_RA] = accum_ticks[GUIDE_DEC] = 0; drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0; out_params.reset(); memset(drift[GUIDE_RA], 0, sizeof(double) * MAX_ACCUM_CNT); memset(drift[GUIDE_DEC], 0, sizeof(double) * MAX_ACCUM_CNT); // cleanup stat vars. sum = 0; preview_mode = false; if (focal > 0 && aperture > 0) createGuideLog(); // Create reference Image if (imageGuideEnabled) { foreach (float *region, referenceRegions) delete[] region; referenceRegions.clear(); referenceRegions = partitionImage(); reticle_pos = Vector(0, 0, 0); } } void cgmath::stop(void) { preview_mode = true; } void cgmath::suspend(bool mode) { suspended = mode; } bool cgmath::isSuspended(void) const { return suspended; } bool cgmath::isStarLost(void) const { return lost_star; } void cgmath::setLostStar(bool is_lost) { lost_star = is_lost; } float *cgmath::createFloatImage(FITSData *target) const { FITSData *imageData = target; if (imageData == nullptr) imageData = guideView->getImageData(); // #1 Convert to float array // We only process 1st plane if it is a color image uint32_t imgSize = imageData->width() * imageData->height(); float *imgFloat = new float[imgSize]; if (imgFloat == nullptr) { qCritical() << "Not enough memory for float image array!"; return nullptr; } switch (imageData->property("dataType").toInt()) { case TBYTE: { uint8_t const *buffer = imageData->getImageBuffer(); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TSHORT: { int16_t const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TUSHORT: { uint16_t const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TLONG: { int32_t const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TULONG: { uint32_t const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TFLOAT: { float const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TLONGLONG: { int64_t const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; case TDOUBLE: { double const *buffer = reinterpret_cast(imageData->getImageBuffer()); for (uint32_t i = 0; i < imgSize; i++) imgFloat[i] = buffer[i]; } break; default: delete[] imgFloat; return nullptr; } return imgFloat; } QVector cgmath::partitionImage() const { QVector regions; FITSData *imageData = guideView->getImageData(); float *imgFloat = createFloatImage(); if (imgFloat == nullptr) return regions; const uint16_t width = imageData->width(); const uint16_t height = imageData->height(); uint8_t xRegions = floor(width / regionAxis); uint8_t yRegions = floor(height / regionAxis); // Find number of regions to divide the image //uint8_t regions = xRegions * yRegions; float *regionPtr = imgFloat; for (uint8_t i = 0; i < yRegions; i++) { for (uint8_t j = 0; j < xRegions; j++) { // Allocate space for one region float *oneRegion = new float[regionAxis * regionAxis]; // Create points to region and current location of the source image in the desired region float *oneRegionPtr = oneRegion, *imgFloatPtr = regionPtr + j * regionAxis; // copy from image to region line by line for (uint32_t line = 0; line < regionAxis; line++) { memcpy(oneRegionPtr, imgFloatPtr, regionAxis); oneRegionPtr += regionAxis; imgFloatPtr += width; } regions.append(oneRegion); } // Move regionPtr block by (width * regionAxis) elements regionPtr += width * regionAxis; } // We're done with imgFloat delete[] imgFloat; return regions; } void cgmath::setRegionAxis(const uint32_t &value) { regionAxis = value; } Vector cgmath::findLocalStarPosition(void) const { if (useRapidGuide) { return Vector(rapidDX, rapidDY, 0); } FITSData *imageData = guideView->getImageData(); if (imageGuideEnabled) { float xshift = 0, yshift = 0; QVector shifts; float xsum = 0, ysum = 0; QVector imagePartition = partitionImage(); if (imagePartition.isEmpty()) { qWarning() << "Failed to partition regions in image!"; return Vector(-1, -1, -1); } if (imagePartition.count() != referenceRegions.count()) { qWarning() << "Mismatch between reference regions #" << referenceRegions.count() << "and image partition regions #" << imagePartition.count(); // Clear memory in case of mis-match foreach (float *region, imagePartition) { delete[] region; } return Vector(-1, -1, -1); } for (uint8_t i = 0; i < imagePartition.count(); i++) { ImageAutoGuiding::ImageAutoGuiding1(referenceRegions[i], imagePartition[i], regionAxis, &xshift, &yshift); Vector shift(xshift, yshift, -1); qCDebug(KSTARS_EKOS_GUIDE) << "Region #" << i << ": X-Shift=" << xshift << "Y-Shift=" << yshift; xsum += xshift; ysum += yshift; shifts.append(shift); } // Delete partitions foreach (float *region, imagePartition) { delete[] region; } imagePartition.clear(); float average_x = xsum / referenceRegions.count(); float average_y = ysum / referenceRegions.count(); float median_x = shifts[referenceRegions.count() / 2 - 1].x; float median_y = shifts[referenceRegions.count() / 2 - 1].y; qCDebug(KSTARS_EKOS_GUIDE) << "Average : X-Shift=" << average_x << "Y-Shift=" << average_y; qCDebug(KSTARS_EKOS_GUIDE) << "Median : X-Shift=" << median_x << "Y-Shift=" << median_y; return Vector(median_x, median_y, -1); } switch (imageData->property("dataType").toInt()) { case TBYTE: return findLocalStarPosition(); case TSHORT: return findLocalStarPosition(); case TUSHORT: return findLocalStarPosition(); case TLONG: return findLocalStarPosition(); case TULONG: return findLocalStarPosition(); case TFLOAT: return findLocalStarPosition(); case TLONGLONG: return findLocalStarPosition(); case TDOUBLE: return findLocalStarPosition(); default: break; } return Vector(-1, -1, -1); } template Vector cgmath::findLocalStarPosition(void) const { static const double P0 = 0.906, P1 = 0.584, P2 = 0.365, P3 = 0.117, P4 = 0.049, P5 = -0.05, P6 = -0.064, P7 = -0.074, P8 = -0.094; Vector ret; int i, j; double resx, resy, mass, threshold, pval; T const *psrc = nullptr; T const *porigin = nullptr; T const *pptr; QRect trackingBox = guideView->getTrackingBox(); if (trackingBox.isValid() == false) return Vector(-1, -1, -1); FITSData *imageData = guideView->getImageData(); if (imageData == nullptr) { qCWarning(KSTARS_EKOS_GUIDE) << "Cannot process a nullptr image."; return Vector(-1, -1, -1); } if (square_alg_idx == SEP_THRESHOLD) { int count = imageData->findStars(ALGORITHM_SEP, trackingBox); if (count > 0) { imageData->getHFR(HFR_MAX); Edge *star = imageData->getMaxHFRStar(); if (star) ret = Vector(star->x, star->y, 0); else ret = Vector(-1, -1, -1); //ret = Vector(star->x, star->y, 0) - Vector(trackingBox.x(), trackingBox.y(), 0); } else ret = Vector(-1, -1, -1); return ret; } T const *pdata = reinterpret_cast(imageData->getImageBuffer()); qCDebug(KSTARS_EKOS_GUIDE) << "Tracking Square " << trackingBox; double square_square = trackingBox.width() * trackingBox.width(); psrc = porigin = pdata + trackingBox.y() * video_width + trackingBox.x(); resx = resy = 0; threshold = mass = 0; // several threshold adaptive smart algorithms switch (square_alg_idx) { case CENTROID_THRESHOLD: { int width = trackingBox.width(); int height = trackingBox.width(); float i0, i1, i2, i3, i4, i5, i6, i7, i8; int ix = 0, iy = 0; int xM4; T const *p; double average, fit, bestFit = 0; int minx = 0; int maxx = width; int miny = 0; int maxy = height; for (int x = minx; x < maxx; x++) for (int y = miny; y < maxy; y++) { i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = 0; xM4 = x - 4; p = psrc + (y - 4) * video_width + xM4; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y - 3) * video_width + xM4; i8 += *p++; i8 += *p++; i8 += *p++; i7 += *p++; i6 += *p++; i7 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y - 2) * video_width + xM4; i8 += *p++; i8 += *p++; i5 += *p++; i4 += *p++; i3 += *p++; i4 += *p++; i5 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y - 1) * video_width + xM4; i8 += *p++; i7 += *p++; i4 += *p++; i2 += *p++; i1 += *p++; i2 += *p++; i4 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y + 0) * video_width + xM4; i8 += *p++; i6 += *p++; i3 += *p++; i1 += *p++; i0 += *p++; i1 += *p++; i3 += *p++; i6 += *p++; i8 += *p++; p = psrc + (y + 1) * video_width + xM4; i8 += *p++; i7 += *p++; i4 += *p++; i2 += *p++; i1 += *p++; i2 += *p++; i4 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y + 2) * video_width + xM4; i8 += *p++; i8 += *p++; i5 += *p++; i4 += *p++; i3 += *p++; i4 += *p++; i5 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y + 3) * video_width + xM4; i8 += *p++; i8 += *p++; i8 += *p++; i7 += *p++; i6 += *p++; i7 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; p = psrc + (y + 4) * video_width + xM4; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; i8 += *p++; average = (i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8) / 85.0; fit = P0 * (i0 - average) + P1 * (i1 - 4 * average) + P2 * (i2 - 4 * average) + P3 * (i3 - 4 * average) + P4 * (i4 - 8 * average) + P5 * (i5 - 4 * average) + P6 * (i6 - 4 * average) + P7 * (i7 - 8 * average) + P8 * (i8 - 48 * average); if (bestFit < fit) { bestFit = fit; ix = x; iy = y; } } if (bestFit > 50) { double sumX = 0; double sumY = 0; double total = 0; for (int y = iy - 4; y <= iy + 4; y++) { p = psrc + y * width + ix - 4; for (int x = ix - 4; x <= ix + 4; x++) { double w = *p++; sumX += x * w; sumY += y * w; total += w; } } if (total > 0) { ret = (Vector(trackingBox.x(), trackingBox.y(), 0) + Vector(sumX / total, sumY / total, 0)); return ret; } } return Vector(-1, -1, -1); } break; // Alexander's Stepanenko smart threshold algorithm case SMART_THRESHOLD: { point_t bbox_lt = { trackingBox.x() - SMART_FRAME_WIDTH, trackingBox.y() - SMART_FRAME_WIDTH }; point_t bbox_rb = { trackingBox.x() + trackingBox.width() + SMART_FRAME_WIDTH, trackingBox.y() + trackingBox.width() + SMART_FRAME_WIDTH }; int offset = 0; // clip frame if (bbox_lt.x < 0) bbox_lt.x = 0; if (bbox_lt.y < 0) bbox_lt.y = 0; if (bbox_rb.x > video_width) bbox_rb.x = video_width; if (bbox_rb.y > video_height) bbox_rb.y = video_height; // calc top bar int box_wd = bbox_rb.x - bbox_lt.x; int box_ht = trackingBox.y() - bbox_lt.y; int pix_cnt = 0; if (box_wd > 0 && box_ht > 0) { pix_cnt += box_wd * box_ht; for (j = bbox_lt.y; j < trackingBox.y(); ++j) { offset = j * video_width; for (i = bbox_lt.x; i < bbox_rb.x; ++i) { pptr = pdata + offset + i; threshold += *pptr; } } } // calc left bar box_wd = trackingBox.x() - bbox_lt.x; box_ht = trackingBox.width(); if (box_wd > 0 && box_ht > 0) { pix_cnt += box_wd * box_ht; for (j = trackingBox.y(); j < trackingBox.y() + box_ht; ++j) { offset = j * video_width; for (i = bbox_lt.x; i < trackingBox.x(); ++i) { pptr = pdata + offset + i; threshold += *pptr; } } } // calc right bar box_wd = bbox_rb.x - trackingBox.x() - trackingBox.width(); box_ht = trackingBox.width(); if (box_wd > 0 && box_ht > 0) { pix_cnt += box_wd * box_ht; for (j = trackingBox.y(); j < trackingBox.y() + box_ht; ++j) { offset = j * video_width; for (i = trackingBox.x() + trackingBox.width(); i < bbox_rb.x; ++i) { pptr = pdata + offset + i; threshold += *pptr; } } } // calc bottom bar box_wd = bbox_rb.x - bbox_lt.x; box_ht = bbox_rb.y - trackingBox.y() - trackingBox.width(); if (box_wd > 0 && box_ht > 0) { pix_cnt += box_wd * box_ht; for (j = trackingBox.y() + trackingBox.width(); j < bbox_rb.y; ++j) { offset = j * video_width; for (i = bbox_lt.x; i < bbox_rb.x; ++i) { pptr = pdata + offset + i; threshold += *pptr; } } } // find maximum double max_val = 0; for (j = 0; j < trackingBox.width(); ++j) { for (i = 0; i < trackingBox.width(); ++i) { pptr = psrc + i; if (*pptr > max_val) max_val = *pptr; } psrc += video_width; } if (pix_cnt != 0) threshold /= (double)pix_cnt; // cut by 10% higher then average threshold if (max_val > threshold) threshold += (max_val - threshold) * SMART_CUT_FACTOR; //log_i("smart thr. = %f cnt = %d", threshold, pix_cnt); break; } // simple adaptive threshold case AUTO_THRESHOLD: { for (j = 0; j < trackingBox.width(); ++j) { for (i = 0; i < trackingBox.width(); ++i) { pptr = psrc + i; threshold += *pptr; } psrc += video_width; } threshold /= square_square; break; } // no threshold subtracion default: { } } psrc = porigin; for (j = 0; j < trackingBox.width(); ++j) { for (i = 0; i < trackingBox.width(); ++i) { pptr = psrc + i; pval = *pptr - threshold; pval = pval < 0 ? 0 : pval; resx += (double)i * pval; resy += (double)j * pval; mass += pval; } psrc += video_width; } if (mass == 0) mass = 1; resx /= mass; resy /= mass; ret = Vector(trackingBox.x(), trackingBox.y(), 0) + Vector(resx, resy, 0); return ret; } void cgmath::process_axes(void) { int cnt = 0; double t_delta = 0; qCDebug(KSTARS_EKOS_GUIDE) << "Processing Axes"; in_params.proportional_gain[0] = Options::rAProportionalGain(); in_params.proportional_gain[1] = Options::dECProportionalGain(); in_params.integral_gain[0] = Options::rAIntegralGain(); in_params.integral_gain[1] = Options::rAIntegralGain(); in_params.derivative_gain[0] = Options::rADerivativeGain(); in_params.derivative_gain[1] = Options::dECDerivativeGain(); in_params.enabled[0] = Options::rAGuideEnabled(); in_params.enabled[1] = Options::dECGuideEnabled(); in_params.min_pulse_length[0] = Options::rAMinimumPulse(); in_params.min_pulse_length[1] = Options::dECMinimumPulse(); in_params.max_pulse_length[0] = Options::rAMaximumPulse(); in_params.max_pulse_length[1] = Options::dECMaximumPulse(); // RA W/E enable // East RA+ enabled? in_params.enabled_axis1[0] = Options::eastRAGuideEnabled(); // West RA- enabled? in_params.enabled_axis2[0] = Options::westRAGuideEnabled(); // DEC N/S enable // North DEC+ enabled? in_params.enabled_axis1[1] = Options::northDECGuideEnabled(); // South DEC- enabled? in_params.enabled_axis2[1] = Options::southDECGuideEnabled(); // process axes... for (int k = GUIDE_RA; k <= GUIDE_DEC; k++) { // zero all out commands out_params.pulse_dir[k] = NO_DIR; if (accum_ticks[k] < in_params.accum_frame_cnt[k] - 1) continue; t_delta = 0; drift_integral[k] = 0; cnt = in_params.accum_frame_cnt[k]; for (int i = 0, idx = channel_ticks[k]; i < cnt; ++i) { t_delta += drift[k][idx]; qCDebug(KSTARS_EKOS_GUIDE) << "At #" << idx << "drift[" << k << "][" << idx << "] = " << drift[k][idx] << " , t_delta: " << t_delta; if (idx > 0) --idx; else idx = MAX_ACCUM_CNT - 1; } for (int i = 0; i < MAX_ACCUM_CNT; ++i) drift_integral[k] += drift[k][i]; out_params.delta[k] = t_delta / (double)cnt; drift_integral[k] /= (double)MAX_ACCUM_CNT; qCDebug(KSTARS_EKOS_GUIDE) << "delta [" << k << "]= " << out_params.delta[k]; qCDebug(KSTARS_EKOS_GUIDE) << "drift_integral[" << k << "]= " << drift_integral[k]; out_params.pulse_length[k] = fabs(out_params.delta[k] * in_params.proportional_gain[k] + drift_integral[k] * in_params.integral_gain[k]); out_params.pulse_length[k] = out_params.pulse_length[k] <= in_params.max_pulse_length[k] ? out_params.pulse_length[k] : in_params.max_pulse_length[k]; qCDebug(KSTARS_EKOS_GUIDE) << "pulse_length [" << k << "]= " << out_params.pulse_length[k]; // calc direction // We do not send pulse if direction is disabled completely, or if direction in a specific axis (e.g. N or S) is disabled if (!in_params.enabled[k] || (out_params.delta[k] > 0 && !in_params.enabled_axis1[k]) || (out_params.delta[k] < 0 && !in_params.enabled_axis2[k])) { out_params.pulse_dir[k] = NO_DIR; out_params.pulse_length[k] = 0; continue; } if (out_params.pulse_length[k] >= in_params.min_pulse_length[k]) { if (k == GUIDE_RA) out_params.pulse_dir[k] = out_params.delta[k] > 0 ? RA_DEC_DIR : RA_INC_DIR; // GUIDE_RA. right dir - decreases GUIDE_RA else { out_params.pulse_dir[k] = out_params.delta[k] > 0 ? DEC_INC_DIR : DEC_DEC_DIR; // GUIDE_DEC. // Reverse DEC direction if we are looking eastward //if (ROT_Z.x[0][0] > 0 || (ROT_Z.x[0][0] ==0 && ROT_Z.x[0][1] > 0)) //out_params.pulse_dir[k] = (out_params.pulse_dir[k] == DEC_INC_DIR) ? DEC_DEC_DIR : DEC_INC_DIR; } } else out_params.pulse_dir[k] = NO_DIR; qCDebug(KSTARS_EKOS_GUIDE) << "Direction : " << get_direction_string(out_params.pulse_dir[k]); } //emit newAxisDelta(out_params.delta[0], out_params.delta[1]); if (Options::guideLogging()) { QTextStream out(&logFile); out << ticks << "," << logTime.elapsed() << "," << out_params.delta[0] << "," << out_params.pulse_length[0] << "," << get_direction_string(out_params.pulse_dir[0]) << "," << out_params.delta[1] << "," << out_params.pulse_length[1] << "," << get_direction_string(out_params.pulse_dir[1]) << endl; } } -void cgmath::performProcessing(void) +void cgmath::performProcessing(GuideLog *logger) { Vector arc_star_pos, arc_reticle_pos; // do nothing if suspended if (suspended) return; // find guiding star location in scr_star_pos = star_pos = findLocalStarPosition(); if (star_pos.x == -1 || std::isnan(star_pos.x)) { lost_star = true; + if (logger != nullptr && !preview_mode) + { + GuideLog::GuideData data; + data.code = GuideLog::GuideData::NO_STAR_FOUND; + data.type = GuideLog::GuideData::DROP; + logger->addGuideData(data); + } return; } else lost_star = false; // move square overlay //TODO FIXME //moveSquare( round(star_pos.x) - (double)square_size/(2*subBinX), round(star_pos.y) - (double)square_size/(2*subBinY) ); QVector3D starCenter(star_pos.x, star_pos.y, 0); emit newStarPosition(starCenter, true); if (preview_mode) return; qCDebug(KSTARS_EKOS_GUIDE) << "################## BEGIN PROCESSING ##################"; // translate star coords into sky coord. system // convert from pixels into arcsecs arc_star_pos = point2arcsec(star_pos); arc_reticle_pos = point2arcsec(reticle_pos); qCDebug(KSTARS_EKOS_GUIDE) << "Star X : " << star_pos.x << " Y : " << star_pos.y; qCDebug(KSTARS_EKOS_GUIDE) << "Reticle X : " << reticle_pos.x << " Y :" << reticle_pos.y; qCDebug(KSTARS_EKOS_GUIDE) << "Star RA: " << arc_star_pos.x << " DEC: " << arc_star_pos.y; qCDebug(KSTARS_EKOS_GUIDE) << "Reticle RA: " << arc_reticle_pos.x << " DEC: " << arc_reticle_pos.y; // translate into sky coords. star_pos = arc_star_pos - arc_reticle_pos; star_pos.y = -star_pos.y; // invert y-axis as y picture axis is inverted qCDebug(KSTARS_EKOS_GUIDE) << "-------> BEFORE ROTATION Diff RA: " << star_pos.x << " DEC: " << star_pos.y; star_pos = star_pos * ROT_Z; // both coords are ready for math processing //put coord to drift list drift[GUIDE_RA][channel_ticks[GUIDE_RA]] = star_pos.x; drift[GUIDE_DEC][channel_ticks[GUIDE_DEC]] = star_pos.y; qCDebug(KSTARS_EKOS_GUIDE) << "-------> AFTER ROTATION Diff RA: " << star_pos.x << " DEC: " << star_pos.y; qCDebug(KSTARS_EKOS_GUIDE) << "RA channel ticks: " << channel_ticks[GUIDE_RA] << " DEC channel ticks: " << channel_ticks[GUIDE_DEC]; // make decision by axes process_axes(); // process statistics calc_square_err(); // finally process tickers do_ticks(); + if (logger != nullptr) + { + GuideLog::GuideData data; + data.type = GuideLog::GuideData::MOUNT; + data.dx = scr_star_pos.x - reticle_pos.x; + data.dy = scr_star_pos.y - reticle_pos.y; + data.raDistance = star_pos.x; + data.decDistance = star_pos.y; + // The guide distances are related to the raw distances above, but + // e.g. small differences can be ignored. We just copy. + data.raGuideDistance = star_pos.x; + data.decGuideDistance = star_pos.y; + data.raDuration = out_params.pulse_length[GUIDE_RA]; + data.raDirection = out_params.pulse_dir[GUIDE_RA]; + data.decDuration = out_params.pulse_length[GUIDE_DEC]; + data.decDirection = out_params.pulse_dir[GUIDE_DEC]; + data.code = GuideLog::GuideData::NO_ERROR; + // Add SNR and MASS from SEP stars. + logger->addGuideData(data); + } qCDebug(KSTARS_EKOS_GUIDE) << "################## FINISH PROCESSING ##################"; } void cgmath::calc_square_err(void) { if (!do_statistics) return; // through MAX_ACCUM_CNT values if (ticks == 0) return; for (int k = GUIDE_RA; k <= GUIDE_DEC; k++) { double sqr_avg = 0; for (int i = 0; i < MAX_ACCUM_CNT; ++i) sqr_avg += drift[k][i] * drift[k][i]; out_params.sigma[k] = sqrt(sqr_avg / (double)MAX_ACCUM_CNT); } } void cgmath::setRapidGuide(bool enable) { useRapidGuide = enable; } double cgmath::getDitherRate(int axis) { if (axis < 0 || axis > 1) return -1; return ditherRate[axis]; } void cgmath::setRapidStarData(double dx, double dy) { rapidDX = dx; rapidDY = dy; } const char *cgmath::get_direction_string(GuideDirection dir) { switch (dir) { case RA_DEC_DIR: return "Decrease RA"; break; case RA_INC_DIR: return "Increase RA"; break; case DEC_DEC_DIR: return "Decrease DEC"; break; case DEC_INC_DIR: return "Increase DEC"; break; default: break; } return "NO DIR"; } bool cgmath::isImageGuideEnabled() const { return imageGuideEnabled; } void cgmath::setImageGuideEnabled(bool value) { imageGuideEnabled = value; } static void psf_conv(float *dst, const float *src, int width, int height) { //dst.Init(src.Size); // A B1 B2 C1 C2 C3 D1 D2 D3 const double PSF[] = { 0.906, 0.584, 0.365, .117, .049, -0.05, -.064, -.074, -.094 }; //memset(dst.px, 0, src.NPixels * sizeof(float)); /* PSF Grid is: D3 D3 D3 D3 D3 D3 D3 D3 D3 D3 D3 D3 D2 D1 D2 D3 D3 D3 D3 D3 C3 C2 C1 C2 C3 D3 D3 D3 D2 C2 B2 B1 B2 C2 D2 D3 D3 D1 C1 B1 A B1 C1 D1 D3 D3 D2 C2 B2 B1 B2 C2 D2 D3 D3 D3 C3 C2 C1 C2 C3 D3 D3 D3 D3 D3 D2 D1 D2 D3 D3 D3 D3 D3 D3 D3 D3 D3 D3 D3 D3 1@A 4@B1, B2, C1, C3, D1 8@C2, D2 44 * D3 */ int psf_size = 4; for (int y = psf_size; y < height - psf_size; y++) { for (int x = psf_size; x < width - psf_size; x++) { float A, B1, B2, C1, C2, C3, D1, D2, D3; #define PX(dx, dy) *(src + width * (y + (dy)) + x + (dx)) A = PX(+0, +0); B1 = PX(+0, -1) + PX(+0, +1) + PX(+1, +0) + PX(-1, +0); B2 = PX(-1, -1) + PX(+1, -1) + PX(-1, +1) + PX(+1, +1); C1 = PX(+0, -2) + PX(-2, +0) + PX(+2, +0) + PX(+0, +2); C2 = PX(-1, -2) + PX(+1, -2) + PX(-2, -1) + PX(+2, -1) + PX(-2, +1) + PX(+2, +1) + PX(-1, +2) + PX(+1, +2); C3 = PX(-2, -2) + PX(+2, -2) + PX(-2, +2) + PX(+2, +2); D1 = PX(+0, -3) + PX(-3, +0) + PX(+3, +0) + PX(+0, +3); D2 = PX(-1, -3) + PX(+1, -3) + PX(-3, -1) + PX(+3, -1) + PX(-3, +1) + PX(+3, +1) + PX(-1, +3) + PX(+1, +3); D3 = PX(-4, -2) + PX(-3, -2) + PX(+3, -2) + PX(+4, -2) + PX(-4, -1) + PX(+4, -1) + PX(-4, +0) + PX(+4, +0) + PX(-4, +1) + PX(+4, +1) + PX(-4, +2) + PX(-3, +2) + PX(+3, +2) + PX(+4, +2); #undef PX int i; const float *uptr; uptr = src + width * (y - 4) + (x - 4); for (i = 0; i < 9; i++) D3 += *uptr++; uptr = src + width * (y - 3) + (x - 4); for (i = 0; i < 3; i++) D3 += *uptr++; uptr += 3; for (i = 0; i < 3; i++) D3 += *uptr++; uptr = src + width * (y + 3) + (x - 4); for (i = 0; i < 3; i++) D3 += *uptr++; uptr += 3; for (i = 0; i < 3; i++) D3 += *uptr++; uptr = src + width * (y + 4) + (x - 4); for (i = 0; i < 9; i++) D3 += *uptr++; double mean = (A + B1 + B2 + C1 + C2 + C3 + D1 + D2 + D3) / 81.0; double PSF_fit = PSF[0] * (A - mean) + PSF[1] * (B1 - 4.0 * mean) + PSF[2] * (B2 - 4.0 * mean) + PSF[3] * (C1 - 4.0 * mean) + PSF[4] * (C2 - 8.0 * mean) + PSF[5] * (C3 - 4.0 * mean) + PSF[6] * (D1 - 4.0 * mean) + PSF[7] * (D2 - 8.0 * mean) + PSF[8] * (D3 - 44.0 * mean); dst[width * y + x] = (float) PSF_fit; } } } static void GetStats(double *mean, double *stdev, int width, const float *img, const QRect &win) { // Determine the mean and standard deviation double sum = 0.0; double a = 0.0; double q = 0.0; double k = 1.0; double km1 = 0.0; const float *p0 = img + win.top() * width + win.left(); for (int y = 0; y < win.height(); y++) { const float *end = p0 + win.height(); for (const float *p = p0; p < end; p++) { double const x = (double) * p; sum += x; double const a0 = a; a += (x - a) / k; q += (x - a0) * (x - a); km1 = k; k += 1.0; } p0 += width; } *mean = sum / km1; *stdev = sqrt(q / km1); } static void RemoveItems(std::set &stars, const std::set &to_erase) { int n = 0; for (std::set::iterator it = stars.begin(); it != stars.end(); n++) { if (to_erase.find(n) != to_erase.end()) { std::set::iterator next = it; ++next; stars.erase(it); it = next; } else ++it; } } // Based on PHD2 algorithm QList cgmath::PSFAutoFind(int extraEdgeAllowance) { //Debug.Write(wxString::Format("Star::AutoFind called with edgeAllowance = %d searchRegion = %d\n", extraEdgeAllowance, searchRegion)); // run a 3x3 median first to eliminate hot pixels //usImage smoothed; //smoothed.CopyFrom(image); //Median3(smoothed); FITSData *smoothed = new FITSData(guideView->getImageData()); smoothed->applyFilter(FITS_MEDIAN); int searchRegion = guideView->getTrackingBox().width(); int subW = smoothed->width(); int subH = smoothed->height(); int size = subW * subH; // convert to floating point float *conv = createFloatImage(smoothed); // run the PSF convolution { float *tmp = new float[size]; memset(tmp, 0, size * sizeof(float)); psf_conv(tmp, conv, subW, subH); delete [] conv; // Swap conv = tmp; } enum { CONV_RADIUS = 4 }; int dw = subW; // width of the downsampled image int dh = subH; // height of the downsampled image QRect convRect(CONV_RADIUS, CONV_RADIUS, dw - 2 * CONV_RADIUS, dh - 2 * CONV_RADIUS); // region containing valid data enum { TOP_N = 100 }; // keep track of the brightest stars std::set stars; // sorted by ascending intensity double global_mean, global_stdev; GetStats(&global_mean, &global_stdev, subW, conv, convRect); //Debug.Write(wxString::Format("AutoFind: global mean = %.1f, stdev %.1f\n", global_mean, global_stdev)); const double threshold = 0.1; //Debug.Write(wxString::Format("AutoFind: using threshold = %.1f\n", threshold)); // find each local maximum int srch = 4; for (int y = convRect.top() + srch; y <= convRect.bottom() - srch; y++) { for (int x = convRect.left() + srch; x <= convRect.right() - srch; x++) { float val = conv[dw * y + x]; bool ismax = false; if (val > 0.0) { ismax = true; for (int j = -srch; j <= srch; j++) { for (int i = -srch; i <= srch; i++) { if (i == 0 && j == 0) continue; if (conv[dw * (y + j) + (x + i)] > val) { ismax = false; break; } } } } if (!ismax) continue; // compare local maximum to mean value of surrounding pixels const int local = 7; double local_mean, local_stdev; QRect localRect(x - local, y - local, 2 * local + 1, 2 * local + 1); localRect = localRect.intersected(convRect); GetStats(&local_mean, &local_stdev, subW, conv, localRect); // this is our measure of star intensity double h = (val - local_mean) / global_stdev; if (h < threshold) { // Debug.Write(wxString::Format("AG: local max REJECT [%d, %d] PSF %.1f SNR %.1f\n", imgx, imgy, val, SNR)); continue; } // coordinates on the original image int downsample = 1; int imgx = x * downsample + downsample / 2; int imgy = y * downsample + downsample / 2; stars.insert(Peak(imgx, imgy, h)); if (stars.size() > TOP_N) stars.erase(stars.begin()); } } //for (std::set::const_reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it) //qCDebug(KSTARS_EKOS_GUIDE) << "AutoFind: local max [" << it->x << "," << it->y << "]" << it->val; // merge stars that are very close into a single star { const int minlimitsq = 5 * 5; repeat: for (std::set::const_iterator a = stars.begin(); a != stars.end(); ++a) { std::set::const_iterator b = a; ++b; for (; b != stars.end(); ++b) { int dx = a->x - b->x; int dy = a->y - b->y; int d2 = dx * dx + dy * dy; if (d2 < minlimitsq) { // very close, treat as single star //Debug.Write(wxString::Format("AutoFind: merge [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val)); // erase the dimmer one stars.erase(a); goto repeat; } } } } // exclude stars that would fit within a single searchRegion box { // build a list of stars to be excluded std::set to_erase; const int extra = 5; // extra safety margin const int fullw = searchRegion + extra; for (std::set::const_iterator a = stars.begin(); a != stars.end(); ++a) { std::set::const_iterator b = a; ++b; for (; b != stars.end(); ++b) { int dx = abs(a->x - b->x); int dy = abs(a->y - b->y); if (dx <= fullw && dy <= fullw) { // stars closer than search region, exclude them both // but do not let a very dim star eliminate a very bright star if (b->val / a->val >= 5.0) { //Debug.Write(wxString::Format("AutoFind: close dim-bright [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val)); } else { //Debug.Write(wxString::Format("AutoFind: too close [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val)); to_erase.insert(std::distance(stars.begin(), a)); to_erase.insert(std::distance(stars.begin(), b)); } } } } RemoveItems(stars, to_erase); } // exclude stars too close to the edge { enum { MIN_EDGE_DIST = 40 }; int edgeDist = MIN_EDGE_DIST;//pConfig->Profile.GetInt("/StarAutoFind/MinEdgeDist", MIN_EDGE_DIST); if (edgeDist < searchRegion) edgeDist = searchRegion; edgeDist += extraEdgeAllowance; std::set::iterator it = stars.begin(); while (it != stars.end()) { std::set::iterator next = it; ++next; if (it->x <= edgeDist || it->x >= subW - edgeDist || it->y <= edgeDist || it->y >= subH - edgeDist) { //Debug.Write(wxString::Format("AutoFind: too close to edge [%d, %d] %.1f\n", it->x, it->y, it->val)); stars.erase(it); } it = next; } } QList centers; for (std::set::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it) { Edge *center = new Edge; center->x = it->x; center->y = it->y; center->val = it->val; centers.append(center); } delete [] conv; delete (smoothed); return centers; } //--------------------------------------------------------------------------------------- cproc_in_params::cproc_in_params() { reset(); } void cproc_in_params::reset(void) { threshold_alg_idx = CENTROID_THRESHOLD; average = true; for (int k = GUIDE_RA; k <= GUIDE_DEC; k++) { enabled[k] = true; accum_frame_cnt[k] = 1; integral_gain[k] = 0; derivative_gain[k] = 0; max_pulse_length[k] = 5000; min_pulse_length[k] = 100; } } cproc_out_params::cproc_out_params() { reset(); } void cproc_out_params::reset(void) { for (int k = GUIDE_RA; k <= GUIDE_DEC; k++) { delta[k] = 0; pulse_dir[k] = NO_DIR; pulse_length[k] = 0; sigma[k] = 0; } } diff --git a/kstars/ekos/guide/internalguide/gmath.h b/kstars/ekos/guide/internalguide/gmath.h index fccbefe91..d8a7d8f13 100644 --- a/kstars/ekos/guide/internalguide/gmath.h +++ b/kstars/ekos/guide/internalguide/gmath.h @@ -1,259 +1,260 @@ /* Ekos guide tool Copyright (C) 2012 Andrew Stepanenko Modified by Jasem Mutlaq for KStars. This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "matr.h" #include "vect.h" #include "indi/indicommon.h" #include #include #include #include #include #include #include +#include "guidelog.h" class FITSView; class FITSData; class Edge; typedef struct { int size; double square; } guide_square_t; #define SMART_THRESHOLD 0 #define SEP_THRESHOLD 1 #define CENTROID_THRESHOLD 2 #define AUTO_THRESHOLD 3 #define NO_THRESHOLD 4 typedef struct { int idx; const char name[32]; } square_alg_t; // smart threshold algorithm param // width of outer frame for background calculation #define SMART_FRAME_WIDTH 4 // cut-factor above average threshold #define SMART_CUT_FACTOR 0.1 #define GUIDE_RA 0 #define GUIDE_DEC 1 #define CHANNEL_CNT 2 #define MAX_ACCUM_CNT 50 extern const guide_square_t guide_squares[]; extern const square_alg_t guide_square_alg[]; // input params class cproc_in_params { public: cproc_in_params(); void reset(void); int threshold_alg_idx; bool enabled[CHANNEL_CNT]; bool enabled_axis1[CHANNEL_CNT]; bool enabled_axis2[CHANNEL_CNT]; bool average; uint32_t accum_frame_cnt[CHANNEL_CNT]; double proportional_gain[CHANNEL_CNT]; double integral_gain[CHANNEL_CNT]; double derivative_gain[CHANNEL_CNT]; int max_pulse_length[CHANNEL_CNT]; int min_pulse_length[CHANNEL_CNT]; }; //output params class cproc_out_params { public: cproc_out_params(); void reset(void); double delta[2]; GuideDirection pulse_dir[2]; int pulse_length[2]; double sigma[2]; }; typedef struct { double focal_ratio; double fov_wd, fov_ht; double focal, aperture; } info_params_t; class cgmath : public QObject { Q_OBJECT public: cgmath(); virtual ~cgmath(); // functions bool setVideoParameters(int vid_wd, int vid_ht, int binX, int binY); bool setGuiderParameters(double ccd_pix_wd, double ccd_pix_ht, double guider_aperture, double guider_focal); void getGuiderParameters(double *ccd_pix_wd, double *ccd_pix_ht, double *guider_aperture, double *guider_focal); bool setReticleParameters(double x, double y, double ang); bool getReticleParameters(double *x, double *y, double *ang) const; int getSquareAlgorithmIndex(void) const; void setSquareAlgorithm(int alg_idx); Ekos::Matrix getROTZ() { return ROT_Z; } const cproc_out_params *getOutputParameters() const { return &out_params; } info_params_t getInfoParameters(void) const; uint32_t getTicks(void) const; void setGuideView(FITSView *image); bool declinationSwapEnabled() { return dec_swap; } void setDeclinationSwapEnabled(bool enable) { dec_swap = enable; } FITSView *getGuideView() { return guideView; } void setPreviewMode(bool enable) { preview_mode = enable; } // Based on PHD2 algorithm QList PSFAutoFind(int extraEdgeAllowance=0); /*void moveSquare( double newx, double newy ); void resizeSquare( int size_idx ); Vector getSquarePosition() { return square_pos; }*/ // Rapid Guide void setRapidGuide(bool enable); void setRapidStarData(double dx, double dy); // Operations void start(void); void stop(void); bool reset(void); void suspend(bool mode); bool isSuspended(void) const; // Star tracking void getStarDrift(double *dx, double *dy) const; void getStarScreenPosition(double *dx, double *dy) const; Vector findLocalStarPosition(void) const; bool isStarLost(void) const; void setLostStar(bool is_lost); // Main processing function - void performProcessing(void); + void performProcessing(GuideLog *logger = nullptr); // Math bool calculateAndSetReticle1D(double start_x, double start_y, double end_x, double end_y, int RATotalPulse = -1); bool calculateAndSetReticle2D(double start_ra_x, double start_ra_y, double end_ra_x, double end_ra_y, double start_dec_x, double start_dec_y, double end_dec_x, double end_dec_y, bool *swap_dec, int RATotalPulse = -1, int DETotalPulse = -1); double calculatePhi(double start_x, double start_y, double end_x, double end_y) const; // Dither double getDitherRate(int axis); bool isImageGuideEnabled() const; void setImageGuideEnabled(bool value); void setRegionAxis(const uint32_t &value); signals: void newAxisDelta(double delta_ra, double delta_dec); void newStarPosition(QVector3D, bool); private: // Templated functions template Vector findLocalStarPosition(void) const; // Creates a new float image from the guideView image data. The returned image MUST be deleted later or memory will leak. float *createFloatImage(FITSData *target=nullptr) const; void do_ticks(void); Vector point2arcsec(const Vector &p) const; void process_axes(void); void calc_square_err(void); const char *get_direction_string(GuideDirection dir); - // Logging + // Old-stye Logging--deprecate. void createGuideLog(); /// Global channel ticker uint32_t ticks { 0 }; /// Pointer to image QPointer guideView; /// Video frame width int video_width { -1 }; /// Video frame height int video_height { -1 }; double ccd_pixel_width { 0 }; double ccd_pixel_height { 0 }; double aperture { 0 }; double focal { 0 }; Ekos::Matrix ROT_Z; bool preview_mode { true }; bool suspended { false }; bool lost_star { false }; bool dec_swap { false }; /// Index of threshold algorithm int square_alg_idx { SMART_THRESHOLD }; int subBinX { 1 }; int subBinY { 1 }; // sky coord. system vars. /// Star position in reticle coord. system Vector star_pos; /// Star position on the screen Vector scr_star_pos; Vector reticle_pos; Vector reticle_orts[2]; double reticle_angle { 0 }; // processing uint32_t channel_ticks[2]; uint32_t accum_ticks[2]; double *drift[2]; double drift_integral[2]; // overlays... cproc_in_params in_params; cproc_out_params out_params; // stat math... bool do_statistics { true }; double sum { 0 }; // rapid guide bool useRapidGuide { false }; double rapidDX { 0 }; double rapidDY { 0 }; // Image Guide bool imageGuideEnabled { false }; // Partition guideView image into NxN square regions each of size axis*axis. The returned vector contains pointers to // the newly allocated square images. It MUST be deleted later by delete[] or memory will leak. QVector partitionImage() const; uint32_t regionAxis { 64 }; QVector referenceRegions; // dithering double ditherRate[2]; QFile logFile; QTime logTime; }; diff --git a/kstars/ekos/guide/internalguide/guidelog.cpp b/kstars/ekos/guide/internalguide/guidelog.cpp new file mode 100644 index 000000000..dd4ef52ac --- /dev/null +++ b/kstars/ekos/guide/internalguide/guidelog.cpp @@ -0,0 +1,299 @@ +/* GuideLog class. + Copyright (C) 2020 Hy Murveit + + This application is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + */ + +#include "guidelog.h" + +#include +#include + +#include +#include +#include + +#include "auxiliary/kspaths.h" +#include + +// This class writes a guide log that is compatible with the phdlogview program. +// See https://openphdguiding.org/phd2-log-viewer/ for details on that program. + +namespace { + +// These conversion aren't correct. I believe the KStars way of doing it, with RA_INC etc +// is better, however, it is consistent and will work with phdlogview. +QString directionString(GuideDirection direction) +{ + switch(direction) + { + case DEC_INC_DIR: return "N"; + case DEC_DEC_DIR: return "S"; + case RA_DEC_DIR: return "E"; + case RA_INC_DIR: return "W"; + case NO_DIR: return ""; + } + return ""; +} + +QString directionStringLong(GuideDirection direction) +{ + switch(direction) + { + case DEC_INC_DIR: return "North"; + case DEC_DEC_DIR: return "South"; + case RA_DEC_DIR: return "East"; + case RA_INC_DIR: return "West"; + case NO_DIR: return ""; + } + return ""; +} + +QString pierSideString(ISD::Telescope::PierSide side) +{ + switch(side) + { + case ISD::Telescope::PierSide::PIER_WEST: return QString("West"); + case ISD::Telescope::PierSide::PIER_EAST: return QString("East"); + case ISD::Telescope::PierSide::PIER_UNKNOWN: return QString("Unknown"); + } + return QString(""); +} + +double degreesToHours(double degrees) +{ + return 24.0 * degrees / 360.0; +} + +} // namespace + +GuideLog::GuideLog() +{ +} + +GuideLog::~GuideLog() +{ + endLog(); +} + +void GuideLog::appendToLog(const QString &lines) +{ + if (!enabled) + return; + QTextStream out(&logFile); + out << lines; + out.flush(); +} + +// Creates the filename and opens the file. +// Prints a line like the one below. +// KStars version 3.4.0. PHD2 log version 2.5. Log enabled at 2019-11-21 00:00:48 +void GuideLog::startLog() +{ + logFileName = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + + "guide_log-" + QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss") + ".txt"; + logFile.setFileName(logFileName); + logFile.open(QIODevice::WriteOnly | QIODevice::Text); + + appendToLog(QString("KStars version %1. PHD2 log version 2.5. Log enabled at %2\n") + .arg(KSTARS_VERSION) + .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))); + + initialized = true; +} + +// Prints a line like the one below and closes the file. +// Log closed at 2019-11-21 08:46:38 +void GuideLog::endLog() +{ + if (!enabled || !initialized) + return; + + if (isGuiding && initialized) + endGuiding(); + + appendToLog(QString("Log closed at %1\n") + .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))); + logFile.close(); +} + +// Output at the start of Guiding. +// Note that in the PHD2 generated versions of this log, there is a lot of guiding information here. +// We just output two lines which phdlogview needs, for pixel scale and RA/DEC. +void GuideLog::startGuiding(const GuideInfo &info) +{ + if (!enabled) + return; + if (!initialized) + startLog(); + + // Currently phdlogview just reads the Pixel scale value on the 2nd line, and + // just reads the Dec value on the 3rd line. + // Note the log wants hrs for RA, the input to this method is in degrees. + appendToLog(QString("Guiding Begins at %1\n" + "Pixel scale = %2 arc-sec/px, Binning = %3, Focal length = %4 mm\n" + "RA = %5 hr, Dec = %6 deg, Hour angle = N/A hr, Pier side = %7, " + "Rotator pos = N/A, Alt = %8 deg, Az = %9 deg\n" + "Frame,Time,mount,dx,dy,RARawDistance,DECRawDistance,RAGuideDistance,DECGuideDistance," + "RADuration,RADirection,DECDuration,DECDirection,XStep,YStep,StarMass,SNR,ErrorCode\n") + .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) + .arg(QString::number(info.pixelScale, 'f', 2)) + .arg(info.binning) + .arg(info.focalLength) + .arg(QString::number(degreesToHours(info.ra), 'f', 2)) + .arg(QString::number(info.dec, 'f', 1)) + .arg(pierSideString(info.pierSide)) + .arg(QString::number(info.altitude, 'f', 1)) + .arg(QString::number(info.azimuth, 'f', 1))); + + guideIndex = 1; + isGuiding = true; + timer.start(); +} + +// Prints a line that looks something like this: +// 55,467.914,"Mount",-1.347,-2.160,2.319,-1.451,1.404,-0.987,303,W,218,N,,,2173,26.91,0 +// See page 56-57 in https://openphdguiding.org/PHD2_User_Guide.pdf for definitions of the fields. +void GuideLog::addGuideData(const GuideData &data) +{ + QString mountString = data.type == GuideData::MOUNT ? "\"Mount\"" : "\"DROP\""; + QString xStepString = ""; + QString yStepString = ""; + appendToLog(QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10,%11,%12,%13,%14,%15,%16,%17,%18\n") + .arg(guideIndex) + .arg(QString::number(timer.elapsed() / 1000.0, 'f', 3)) + .arg(mountString) + .arg(QString::number(data.dx, 'f', 3)) + .arg(QString::number(data.dy, 'f', 3)) + .arg(QString::number(data.raDistance, 'f', 3)) + .arg(QString::number(data.decDistance, 'f', 3)) + .arg(QString::number(data.raGuideDistance, 'f', 3)) + .arg(QString::number(data.decGuideDistance, 'f', 3)) + .arg(data.raDuration) + .arg(directionString(data.raDirection)) + .arg(data.decDuration) + .arg(directionString(data.decDirection)) + .arg(xStepString) + .arg(yStepString) + .arg(QString::number(data.mass, 'f', 0)) + .arg(QString::number(data.snr, 'f', 2)) + .arg(static_cast(data.code))); + ++guideIndex; +} + +// Prints a line that looks like: +// Guiding Ends at 2019-11-21 01:57:45 +void GuideLog::endGuiding() +{ + appendToLog(QString("Guiding Ends at %1\n") + .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))); + isGuiding = false; +} + +// Note that in the PHD2 generated versions of this log, there is a lot of calibration information here. +// We just output two lines which phdlogview needs, for pixel scale and RA/DEC. +void GuideLog::startCalibration(const GuideInfo &info) +{ + if (!enabled) + return; + if (!initialized) + startLog(); + // Currently phdlogview just reads the Pixel scale value on the 2nd line, and + // just reads the Dec value on the 3rd line. + appendToLog(QString("Calibration Begins at %1\n" + "Pixel scale = %2 arc-sec/px, Binning = %3, Focal length = %4 mm\n" + "RA = %5 hr, Dec = %6 deg, Hour angle = N/A hr, Pier side = %7, " + "Rotator pos = N/A, Alt = %8 deg, Az = %9 deg\n" + "Direction,Step,dx,dy,x,y,Dist\n") + .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) + .arg(QString::number(info.pixelScale, 'f', 2)) + .arg(info.binning) + .arg(info.focalLength) + .arg(QString::number(degreesToHours(info.ra), 'f', 2)) + .arg(QString::number(info.dec, 'f', 1)) + .arg(pierSideString(info.pierSide)) + .arg(QString::number(info.altitude, 'f', 1)) + .arg(QString::number(info.azimuth, 'f', 1))); + + calibrationIndex = 1; + timer.start(); + lastCalibrationDirection = NO_DIR; +} + +// Prints a line that looks like: +// West,2,-15.207,-1.037,54.800,58.947,15.242 +void GuideLog::addCalibrationData(GuideDirection direction, double x, double y, double xOrigin, double yOrigin) +{ + if (direction != lastCalibrationDirection) + calibrationIndex = 1; + lastCalibrationDirection = direction; + + appendToLog(QString("%1,%2,%3,%4,%5,%6,%7\n") + .arg(directionStringLong(direction)) + .arg(calibrationIndex) + .arg(QString::number(x - xOrigin, 'f', 3)) + .arg(QString::number(y - yOrigin, 'f', 3)) + .arg(QString::number(x, 'f', 3)) + .arg(QString::number(y, 'f', 3)) + .arg(QString::number(hypot(x - xOrigin, y - yOrigin), 'f', 3))); + + // This is a little different than PHD2--they seem to count down in the reverse directions. + calibrationIndex++; +} + +// Prints a line that looks like: +// West calibration complete. Angle = 106.8 deg +// Currently phdlogview ignores this line. +void GuideLog::endCalibrationSection(GuideDirection direction, double degrees) +{ + appendToLog(QString("%1 calibration complete. Angle = %2 deg\n") + .arg(directionStringLong(direction)) + .arg(QString::number(degrees, 'f', 1))); +} + +// Prints two lines that look like: +// Calibration guide speeds: RA = 191.5 a-s/s, Dec = 408.0 a-s/s +// Calibration complete +// The failed version is not in the PHD2 log, will be ignored by the viewer. +void GuideLog::endCalibration(double raSpeed, double decSpeed) +{ + if (raSpeed == 0 && decSpeed == 0) + appendToLog(QString("Calibration complete (Failed)\n")); + else + appendToLog(QString("Calibration guide speeds: RA = %1 a-s/s, Dec = %2 a-s/s\n" + "Calibration complete\n") + .arg(QString::number(raSpeed, 'f', 1)) + .arg(QString::number(decSpeed, 'f', 1))); +} + +void GuideLog::ditherInfo(double dx, double dy, double x, double y) +{ + appendToLog(QString("INFO: DITHER by %1, %2, new lock pos = %3, %4\n") + .arg(QString::number(dx, 'f', 3)) + .arg(QString::number(dy, 'f', 3)) + .arg(QString::number(x, 'f', 3)) + .arg(QString::number(y, 'f', 3))); +} + +void GuideLog::pauseInfo() +{ + appendToLog("INFO: Server received PAUSE\n"); +} + +void GuideLog::resumeInfo() +{ + appendToLog("INFO: Server received RESUME\n"); +} + +void GuideLog::settleStartedInfo() +{ + appendToLog("INFO: SETTLING STATE CHANGE, Settling started\n"); +} + +void GuideLog::settleCompletedInfo() +{ + appendToLog("INFO: SETTLING STATE CHANGE, Settling complete\n"); +} diff --git a/kstars/ekos/guide/internalguide/guidelog.h b/kstars/ekos/guide/internalguide/guidelog.h new file mode 100644 index 000000000..9386ed272 --- /dev/null +++ b/kstars/ekos/guide/internalguide/guidelog.h @@ -0,0 +1,112 @@ +/* GuideLog class. + Copyright (C) 2020 Hy Murveit + + This application is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + */ + +#pragma once + +#include +#include + +#include "indi/indicommon.h" +#include "indi/inditelescope.h" + +// This class will help write guide log files, using the PHD2 guide log format. + +class GuideLog +{ +public: + class GuideInfo + { + public: + double pixelScale = 0; + int binning = 1; + double focalLength = 0; + // Recent mount position. + double ra = 0, dec = 0, azimuth = 0, altitude = 0; + ISD::Telescope::PierSide pierSide; + }; + + class GuideData + { + public: + enum GuideDataType { MOUNT, DROP }; + GuideDataType type; + double dx, dy; + double raDistance, decDistance; + double raGuideDistance, decGuideDistance; + int raDuration, decDuration; + GuideDirection raDirection, decDirection; + double mass; + double snr; + // From https://openphdguiding.org/PHD2_User_Guide.pdf and logs + enum ErrorCode { + NO_ERROR = 0, + STAR_SATURATED = 1, + LOW_SNR = 2, + STAR_LOST_LOW_MASS = 3, + EDGE_OF_FRAME = 4, + STAR_MASS_CHANGED = 5, + STAR_LOST_MASS_CHANGED = 6, + NO_STAR_FOUND = 7 + }; + ErrorCode code; + }; + + GuideLog(); + ~GuideLog(); + + // Won't log unless enable() is called. + void enable() { enabled = true; } + void disable() { enabled = false; } + + // These are called for each guiding session. + void startGuiding(const GuideInfo &info); + void addGuideData(const GuideData &data); + void endGuiding(); + + // These are called for each calibration session. + void startCalibration(const GuideInfo &info); + void addCalibrationData(GuideDirection direction, double x, double y, double xOrigin, double yOrigin); + void endCalibrationSection(GuideDirection direction, double degrees); + void endCalibration(double raSpeed, double decSpeed); + + // INFO messages + void ditherInfo(double dx, double dy, double x, double y); + void pauseInfo(); + void resumeInfo(); + void settleStartedInfo(); + void settleCompletedInfo(); + + // Deal with suspend, resume, dither, ... +private: + // Write the file header and footer. + void startLog(); + void endLog(); + void appendToLog(const QString &lines); + + // Log file info. + QFile logFile; + QString logFileName; + + // Message indeces and timers. + int guideIndex = 1; + int calibrationIndex = 1; + QElapsedTimer timer; + + // Used to write and end-of-guiding message on exit, if this was not called. + bool isGuiding = false; + + // Variable used to detect calibration change of direction. + GuideDirection lastCalibrationDirection = NO_DIR; + + // If false, no logging will occur. + bool enabled = false; + + // True means the filename was created and the log's header has been written. + bool initialized = false; +}; diff --git a/kstars/ekos/guide/internalguide/internalguider.cpp b/kstars/ekos/guide/internalguide/internalguider.cpp index d99e8dffe..c160e496c 100644 --- a/kstars/ekos/guide/internalguide/internalguider.cpp +++ b/kstars/ekos/guide/internalguide/internalguider.cpp @@ -1,1370 +1,1438 @@ /* Ekos Internal Guider Class Copyright (C) 2016 Jasem Mutlaq . Based on lin_guider This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "internalguider.h" #include "ekos_guide_debug.h" #include "gmath.h" #include "Options.h" #include "auxiliary/kspaths.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "ksnotification.h" #include #include #include #include #define MAX_GUIDE_STARS 10 namespace Ekos { InternalGuider::InternalGuider() { // Create math object pmath.reset(new cgmath()); connect(pmath.get(), &cgmath::newStarPosition, this, &InternalGuider::newStarPosition); state = GUIDE_IDLE; } bool InternalGuider::guide() { if (state == GUIDE_SUSPENDED) return true; if (state >= GUIDE_GUIDING) { if (m_ImageGuideEnabled) return processImageGuiding(); else return processGuiding(); } guideFrame->disconnect(this); pmath->start(); m_starLostCounter = 0; m_highRMSCounter = 0; // TODO re-enable rapid check later on #if 0 m_isStarted = true; m_useRapidGuide = ui.rapidGuideCheck->isChecked(); if (m_useRapidGuide) guideModule->startRapidGuide(); emit newStatus(Ekos::GUIDE_GUIDING); guideModule->setSuspended(false); first_frame = true; if (ui.subFrameCheck->isEnabled() && ui.subFrameCheck->isChecked() && m_isSubFramed == false) first_subframe = true; capture(); #endif m_isFirstFrame = true; + if (state == GUIDE_IDLE) + { + if (Options::saveGuideLog()) + guideLog.enable(); + GuideLog::GuideInfo info; + fillGuideInfo(&info); + guideLog.startGuiding(info); + } + state = GUIDE_GUIDING; emit newStatus(state); emit frameCaptureRequested(); return true; } bool InternalGuider::abort() { calibrationStage = CAL_IDLE; logFile.close(); + guideLog.endGuiding(); if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) { if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) emit newStatus(GUIDE_DITHERING_ERROR); emit newStatus(GUIDE_ABORTED); qCDebug(KSTARS_EKOS_GUIDE) << "Aborting" << getGuideStatusString(state); } else { emit newStatus(GUIDE_IDLE); qCDebug(KSTARS_EKOS_GUIDE) << "Stopping internal guider."; } m_ProgressiveDither.clear(); m_starLostCounter = 0; m_highRMSCounter = 0; accumulator.first = accumulator.second = 0; pmath->suspend(false); state = GUIDE_IDLE; return true; } bool InternalGuider::suspend() { + guideLog.pauseInfo(); state = GUIDE_SUSPENDED; emit newStatus(state); pmath->suspend(true); return true; } bool InternalGuider::resume() { + guideLog.resumeInfo(); state = GUIDE_GUIDING; emit newStatus(state); pmath->suspend(false); emit frameCaptureRequested(); return true; } bool InternalGuider::ditherXY(double x, double y) { m_ProgressiveDither.clear(); m_DitherRetries = 0; double cur_x, cur_y, ret_angle; pmath->getReticleParameters(&cur_x, &cur_y, &ret_angle); // Find out how many "jumps" we need to perform in order to get to target. // The current limit is now 1/4 of the box size to make sure the star stays within detection // threashold inside the window. double oneJump = (guideBoxSize / 4.0); double targetX = cur_x, targetY = cur_y; int xSign = (x >= cur_x) ? 1 : -1; int ySign = (y >= cur_y) ? 1 : -1; do { if (fabs(targetX - x) > oneJump) targetX += oneJump * xSign; else if (fabs(targetX - x) < oneJump) targetX = x; if (fabs(targetY - y) > oneJump) targetY += oneJump * ySign; else if (fabs(targetY - y) < oneJump) targetY = y; m_ProgressiveDither.enqueue(Vector(targetX, targetY, ret_angle)); } while (targetX != x || targetY != y); m_DitherTargetPosition = m_ProgressiveDither.dequeue(); pmath->setReticleParameters(m_DitherTargetPosition.x, m_DitherTargetPosition.y, m_DitherTargetPosition.z); + guideLog.ditherInfo(x, y, m_DitherTargetPosition.x, m_DitherTargetPosition.y); state = GUIDE_MANUAL_DITHERING; emit newStatus(state); processGuiding(); return true; } bool InternalGuider::dither(double pixels) { double cur_x, cur_y, ret_angle; pmath->getReticleParameters(&cur_x, &cur_y, &ret_angle); pmath->getStarScreenPosition(&cur_x, &cur_y); Ekos::Matrix ROT_Z = pmath->getROTZ(); if (state != GUIDE_DITHERING) { m_DitherRetries = 0; auto seed = std::chrono::system_clock::now().time_since_epoch().count(); std::default_random_engine generator(seed); std::uniform_real_distribution angleMagnitude(0, 360); double angle = angleMagnitude(generator) * dms::DegToRad; double diff_x = pixels * cos(angle); double diff_y = pixels * sin(angle); if (pmath->declinationSwapEnabled()) diff_y *= -1; if (fabs(diff_x + accumulator.first) > MAX_DITHER_TRAVEL) diff_x *= -1.5; accumulator.first += diff_x; if (fabs(diff_y + accumulator.second) > MAX_DITHER_TRAVEL) diff_y *= -1.5; accumulator.second += diff_y; m_DitherTargetPosition = Vector(cur_x, cur_y, 0) + Vector(diff_x, diff_y, 0); qCDebug(KSTARS_EKOS_GUIDE) << "Dithering process started.. Reticle Target Pos X " << m_DitherTargetPosition.x << " Y " << m_DitherTargetPosition.y; + guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y); pmath->setReticleParameters(m_DitherTargetPosition.x, m_DitherTargetPosition.y, ret_angle); state = GUIDE_DITHERING; emit newStatus(state); processGuiding(); return true; } Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0); + star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "Dithering in progress. Diff star X:" << star_pos.x << "Y:" << star_pos.y; + if (fabs(star_pos.x) < 1 && fabs(star_pos.y) < 1) { pmath->setReticleParameters(cur_x, cur_y, ret_angle); qCDebug(KSTARS_EKOS_GUIDE) << "Dither complete."; if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; + guideLog.settleStartedInfo(); emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); } else { if (++m_DitherRetries > Options::ditherMaxIterations()) { if (Options::ditherFailAbortsAutoGuide()) { emit newStatus(Ekos::GUIDE_DITHERING_ERROR); abort(); return false; } else { emit newLog(i18n("Warning: Dithering failed. Autoguiding shall continue as set in the options in case " "of dither failure.")); if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; + guideLog.settleStartedInfo(); emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); return true; } } processGuiding(); } return true; } bool InternalGuider::processManualDithering() { double cur_x, cur_y, ret_angle; pmath->getReticleParameters(&cur_x, &cur_y, &ret_angle); pmath->getStarScreenPosition(&cur_x, &cur_y); Ekos::Matrix ROT_Z = pmath->getROTZ(); Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dithering in progress. Diff star X:" << star_pos.x << "Y:" << star_pos.y; if (fabs(star_pos.x) < guideBoxSize / 5.0 && fabs(star_pos.y) < guideBoxSize / 5.0) { if (m_ProgressiveDither.empty() == false) { m_DitherTargetPosition = m_ProgressiveDither.dequeue(); pmath->setReticleParameters(m_DitherTargetPosition.x, m_DitherTargetPosition.y, m_DitherTargetPosition.z); qCDebug(KSTARS_EKOS_GUIDE) << "Next Dither Jump X:" << m_DitherTargetPosition.x << "Jump Y:" << m_DitherTargetPosition.y; m_DitherRetries = 0; processGuiding(); return true; } if (fabs(star_pos.x) < 1 && fabs(star_pos.y) < 1) { pmath->setReticleParameters(cur_x, cur_y, ret_angle); qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dither complete."; if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; + guideLog.settleStartedInfo(); emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); } else { processGuiding(); } } else { if (++m_DitherRetries > Options::ditherMaxIterations()) { emit newLog(i18n("Warning: Manual Dithering failed.")); if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; + guideLog.settleStartedInfo(); emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); return true; } processGuiding(); } return true; } void InternalGuider::setDitherSettled() { + guideLog.settleCompletedInfo(); emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); // Back to guiding state = GUIDE_GUIDING; } bool InternalGuider::calibrate() { bool ccdInfo = true, scopeInfo = true; QString errMsg; if (subW == 0 || subH == 0) { errMsg = "CCD"; ccdInfo = false; } if (mountAperture == 0.0 || mountFocalLength == 0.0) { scopeInfo = false; if (ccdInfo == false) errMsg += " & Telescope"; else errMsg += "Telescope"; } if (ccdInfo == false || scopeInfo == false) { KSNotification::error(i18n("%1 info are missing. Please set the values in INDI Control Panel.", errMsg), i18n("Missing Information")); return false; } if (state != GUIDE_CALIBRATING) { calibrationStage = CAL_IDLE; state = GUIDE_CALIBRATING; emit newStatus(GUIDE_CALIBRATING); } if (calibrationStage > CAL_START) { processCalibration(); return true; } guideFrame->disconnect(this); // Must reset dec swap before we run any calibration procedure! emit DESwapChanged(false); pmath->setDeclinationSwapEnabled(false); pmath->setLostStar(false); calibrationStage = CAL_START; + if (Options::saveGuideLog()) + guideLog.enable(); + GuideLog::GuideInfo info; + fillGuideInfo(&info); + guideLog.startCalibration(info); // automatic // If two axies (RA/DEC) are required if (Options::twoAxisEnabled()) calibrateRADECRecticle(false); else // Just RA calibrateRADECRecticle(true); return true; } void InternalGuider::processCalibration() { pmath->performProcessing(); if (pmath->isStarLost()) { emit newLog(i18n("Lost track of the guide star. Try increasing the square size or reducing pulse duration.")); reset(); calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: Lost guide star.")); return; } switch (calibrationType) { case CAL_NONE: break; case CAL_RA_AUTO: calibrateRADECRecticle(true); break; case CAL_RA_DEC_AUTO: calibrateRADECRecticle(false); break; } } void InternalGuider::setGuideView(FITSView *guideView) { guideFrame = guideView; pmath->setGuideView(guideFrame); } void InternalGuider::reset() { state = GUIDE_IDLE; //calibrationStage = CAL_IDLE; connect(guideFrame, SIGNAL(trackingStarSelected(int, int)), this, SLOT(trackingStarSelected(int, int)), Qt::UniqueConnection); } void InternalGuider::calibrateRADECRecticle(bool ra_only) { bool axis_calibration_complete = false; Q_ASSERT(pmath); //int totalPulse = pulseDuration * Options::autoModeIterations(); if (ra_only) calibrationType = CAL_RA_AUTO; else calibrationType = CAL_RA_DEC_AUTO; switch (calibrationStage) { case CAL_START: //----- automatic mode ----- m_CalibrationParams.auto_drift_time = Options::autoModeIterations(); m_CalibrationParams.turn_back_time = m_CalibrationParams.auto_drift_time * 7; m_CalibrationParams.ra_iterations = 0; m_CalibrationParams.dec_iterations = 0; m_CalibrationParams.ra_total_pulse = m_CalibrationParams.de_total_pulse = 0; emit newLog(i18n("RA drifting forward...")); pmath->getReticleParameters(&m_CalibrationCoords.start_x1, &m_CalibrationCoords.start_y1, nullptr); m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); emit calibrationUpdate(GuideInterface::RA_IN, i18n("Guide Star found."), 0, 0); qCDebug(KSTARS_EKOS_GUIDE) << "Auto Iteration #" << m_CalibrationParams.auto_drift_time << "Default pulse:" << m_CalibrationParams.last_pulse; qCDebug(KSTARS_EKOS_GUIDE) << "Start X1 " << m_CalibrationCoords.start_x1 << " Start Y1 " << m_CalibrationCoords.start_y1; axis_calibration_complete = false; m_CalibrationCoords.last_x = m_CalibrationCoords.start_x1; m_CalibrationCoords.last_y = m_CalibrationCoords.start_x2; emit newPulse(RA_INC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; calibrationStage = CAL_RA_INC; + guideLog.addCalibrationData( + RA_INC_DIR, + m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, + m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1); break; case CAL_RA_INC: { // Star position resulting from LAST guiding pulse to mount double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); emit calibrationUpdate(GuideInterface::RA_IN, i18n("Calibrating RA Out"), cur_x - m_CalibrationCoords.start_x1, cur_y - m_CalibrationCoords.start_y1); qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.ra_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.ra_iterations << " Direction: RA_INC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; + guideLog.addCalibrationData(RA_INC_DIR, cur_x, cur_y, + m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1); + // Must pass at least 1.5 pixels to move on to the next stage if (m_CalibrationParams.ra_iterations >= m_CalibrationParams.auto_drift_time && (fabs(cur_x - m_CalibrationCoords.start_x1) > 1.5 || fabs(cur_y - m_CalibrationCoords.start_y1) > 1.5)) { m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; calibrationStage = CAL_RA_DEC; m_CalibrationCoords.end_x1 = cur_x; m_CalibrationCoords.end_y1 = cur_y; m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "End X1 " << m_CalibrationCoords.end_x1 << " End Y1 " << m_CalibrationCoords.end_y1; m_CalibrationParams.phi = pmath->calculatePhi(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, m_CalibrationCoords.end_x1, m_CalibrationCoords.end_y1); ROT_Z = RotateZ(-M_PI * m_CalibrationParams.phi / 180.0); // derotates... m_CalibrationCoords.ra_distance = 0; m_CalibrationParams.backlash = 0; emit newPulse(RA_DEC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; emit newLog(i18n("RA drifting reverse...")); + guideLog.endCalibrationSection(RA_INC_DIR, m_CalibrationParams.phi); } else if (m_CalibrationParams.ra_iterations > m_CalibrationParams.turn_back_time) { emit newLog(i18n("Calibration rejected. Star drift is too short. Check for mount, cable, or backlash problems.")); calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: Drift too short.")); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); + guideLog.endCalibration(0, 0); reset(); } else { // Aggressive pulse in case we're going slow if (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) { // 200% m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; } else { m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; emit newPulse(RA_INC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; } } break; case CAL_RA_DEC: { //----- Z-check (new!) ----- double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); emit calibrationUpdate(GuideInterface::RA_OUT, i18n("Calibrating RA In"), cur_x - m_CalibrationCoords.start_x1, cur_y - m_CalibrationCoords.start_y1); qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.ra_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.ra_iterations << " Direction: RA_DEC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "Star x pos is " << star_pos.x << " from original point."; if (m_CalibrationCoords.ra_distance == 0.0) m_CalibrationCoords.ra_distance = star_pos.x; + guideLog.addCalibrationData(RA_DEC_DIR, cur_x, cur_y, + m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1); + // start point reached... so exit if (star_pos.x < 1.5) { pmath->performProcessing(); m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); axis_calibration_complete = true; } // If we'not moving much, try increasing pulse to 200% to clear any backlash // Also increase pulse width if we are going FARTHER and not back to our original position else if ( (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) || star_pos.x > m_CalibrationCoords.ra_distance) { m_CalibrationParams.backlash++; // Increase pulse to 200% after we tried to fight against backlash 2 times at least if (m_CalibrationParams.backlash > 2) m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; else m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } else { m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); m_CalibrationParams.backlash = 0; } m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; //----- Z-check end ----- if (axis_calibration_complete == false) { if (m_CalibrationParams.ra_iterations < m_CalibrationParams.turn_back_time) { emit newPulse(RA_DEC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; break; } calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: couldn't reach start.")); emit newLog(i18np("Guide RA: Scope cannot reach the start point after %1 iteration. Possible mount or " "backlash problems...", "GUIDE_RA: Scope cannot reach the start point after %1 iterations. Possible mount or " "backlash problems...", m_CalibrationParams.ra_iterations)); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); reset(); break; } if (ra_only == false) { calibrationStage = CAL_DEC_INC; m_CalibrationCoords.start_x2 = cur_x; m_CalibrationCoords.start_y2 = cur_y; m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Start X2 " << m_CalibrationCoords.start_x2 << " start Y2 " << m_CalibrationCoords.start_y2; emit newPulse(DEC_INC_DIR, Options::calibrationPulseDuration()); m_CalibrationParams.dec_iterations++; emit newLog(i18n("DEC drifting forward...")); break; } // calc orientation if (pmath->calculateAndSetReticle1D(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, m_CalibrationCoords.end_x1, m_CalibrationCoords.end_y1, m_CalibrationParams.ra_total_pulse)) { calibrationStage = CAL_IDLE; emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); KSNotification::event(QLatin1String("CalibrationSuccessful"), i18n("Guiding calibration completed successfully")); } else { emit newLog(i18n("Calibration rejected. Star drift is too short. Check for mount, cable, or backlash problems.")); calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: drift too short.")); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); } reset(); break; } case CAL_DEC_INC: { // Star position resulting from LAST guiding pulse to mount double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); emit calibrationUpdate(GuideInterface::DEC_IN, i18n("Calibrating DEC Out"), cur_x - m_CalibrationCoords.start_x2, cur_y - m_CalibrationCoords.start_y2); qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.dec_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.dec_iterations << " Direction: DEC_INC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; + // Don't yet know how to tell NORTH vs SOUTH + guideLog.addCalibrationData(DEC_INC_DIR, cur_x, cur_y, + m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2); + if (m_CalibrationParams.dec_iterations >= m_CalibrationParams.auto_drift_time && (fabs(cur_x - m_CalibrationCoords.start_x2) > 1.5 || fabs(cur_y - m_CalibrationCoords.start_y2) > 1.5)) { calibrationStage = CAL_DEC_DEC; m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationCoords.end_x2 = cur_x; m_CalibrationCoords.end_y2 = cur_y; m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; axis_calibration_complete = false; qCDebug(KSTARS_EKOS_GUIDE) << "End X2 " << m_CalibrationCoords.end_x2 << " End Y2 " << m_CalibrationCoords.end_y2; m_CalibrationParams.phi = pmath->calculatePhi(m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2, m_CalibrationCoords.end_x2, m_CalibrationCoords.end_y2); ROT_Z = RotateZ(-M_PI * m_CalibrationParams.phi / 180.0); // derotates... m_CalibrationCoords.de_distance = 0; emit newPulse(DEC_DEC_DIR, m_CalibrationParams.last_pulse); emit newLog(i18n("DEC drifting reverse...")); m_CalibrationParams.dec_iterations++; + guideLog.endCalibrationSection(DEC_INC_DIR, m_CalibrationParams.phi); } else if (m_CalibrationParams.dec_iterations > m_CalibrationParams.turn_back_time) { calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: couldn't reach start point.")); emit newLog(i18np("Guide DEC: Scope cannot reach the start point after %1 iteration.\nPossible mount " "or backlash problems...", "GUIDE DEC: Scope cannot reach the start point after %1 iterations.\nPossible mount " "or backlash problems...", m_CalibrationParams.dec_iterations)); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); + guideLog.endCalibration(0, 0); reset(); } else { if (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) { // Increase pulse by 200% m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; } else { m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; emit newPulse(DEC_INC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.dec_iterations++; } } break; case CAL_DEC_DEC: { //----- Z-check (new!) ----- double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); emit calibrationUpdate(GuideInterface::DEC_OUT, i18n("Calibrating DEC In"), cur_x - m_CalibrationCoords.start_x2, cur_y - m_CalibrationCoords.start_y2); // Star position resulting from LAST guiding pulse to mount qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.dec_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.dec_iterations << " Direction: DEC_DEC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "start Pos X " << star_pos.x << " from original point."; // Keep track of distance if (m_CalibrationCoords.de_distance == 0.0) m_CalibrationCoords.de_distance = star_pos.x; + // South? + guideLog.addCalibrationData(DEC_DEC_DIR, cur_x, cur_y, + m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2); + // start point reached... so exit if (star_pos.x < 1.5) { pmath->performProcessing(); m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); axis_calibration_complete = true; } // Increase pulse if we're not moving much or if we are moving _away_ from target. else if ( (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) || star_pos.x > m_CalibrationCoords.de_distance) { // Increase pulse by 200% m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; } else { m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } if (axis_calibration_complete == false) { if (m_CalibrationParams.dec_iterations < m_CalibrationParams.turn_back_time) { emit newPulse(DEC_DEC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.dec_iterations++; break; } calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: couldn't reach start point.")); emit newLog(i18np("Guide DEC: Scope cannot reach the start point after %1 iteration.\nPossible mount " "or backlash problems...", "Guide DEC: Scope cannot reach the start point after %1 iterations.\nPossible mount " "or backlash problems...", m_CalibrationParams.dec_iterations)); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); reset(); break; } bool swap_dec = false; // calc orientation if (pmath->calculateAndSetReticle2D(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, m_CalibrationCoords.end_x1, m_CalibrationCoords.end_y1, m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2, m_CalibrationCoords.end_x2, m_CalibrationCoords.end_y2, &swap_dec, m_CalibrationParams.ra_total_pulse, m_CalibrationParams.de_total_pulse)) { calibrationStage = CAL_IDLE; //fillInterface(); if (swap_dec) emit newLog(i18n("DEC swap enabled.")); else emit newLog(i18n("DEC swap disabled.")); emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); emit DESwapChanged(swap_dec); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Successful")); KSNotification::event(QLatin1String("CalibrationSuccessful"), i18n("Guiding calibration completed successfully")); //if (ui.autoStarCheck->isChecked()) //guideModule->selectAutoStar(); + + // Fill in mount + guideLog.endCalibration(pmath->getDitherRate(0), pmath->getDitherRate(1)); } else { emit newLog(i18n("Calibration rejected. Star drift is too short. Check for mount, cable, or backlash problems.")); emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: drift too short.")); emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); //ui.startCalibrationLED->setColor(alertColor); calibrationStage = CAL_ERROR; KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); + guideLog.endCalibration(0, 0); } reset(); break; } default: break; } } void InternalGuider::setStarPosition(QVector3D &starCenter) { pmath->setReticleParameters(starCenter.x(), starCenter.y(), -1); } void InternalGuider::trackingStarSelected(int x, int y) { if (calibrationStage == CAL_IDLE) return; //int square_size = guide_squares[pmath->getSquareIndex()].size; pmath->setReticleParameters(x, y, -1); //pmath->moveSquare(x-square_size/(2*pmath->getBinX()), y-square_size/(2*pmath->getBinY())); //update_reticle_pos(x, y); //ui.selectStarLED->setColor(okColor); calibrationStage = CAL_START; //ui.pushButton_StartCalibration->setEnabled(true); /*QVector3D starCenter; starCenter.setX(x); starCenter.setY(y); emit newStarPosition(starCenter, true);*/ //if (ui.autoStarCheck->isChecked()) //if (Options::autoStarEnabled()) //calibrate(); } void InternalGuider::setDECSwap(bool enable) { pmath->setDeclinationSwapEnabled(enable); } void InternalGuider::setSquareAlgorithm(int index) { pmath->setSquareAlgorithm(index); } void InternalGuider::setReticleParameters(double x, double y, double angle) { pmath->setReticleParameters(x, y, angle); } bool InternalGuider::getReticleParameters(double *x, double *y, double *angle) { return pmath->getReticleParameters(x, y, angle); } bool InternalGuider::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength) { this->ccdPixelSizeX = ccdPixelSizeX; this->ccdPixelSizeY = ccdPixelSizeY; this->mountAperture = mountAperture; this->mountFocalLength = mountFocalLength; return pmath->setGuiderParameters(ccdPixelSizeX, ccdPixelSizeY, mountAperture, mountFocalLength); } bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY) { if (w <= 0 || h <= 0) return false; subX = x; subY = y; subW = w; subH = h; subBinX = binX; subBinY = binY; pmath->setVideoParameters(w, h, subBinX, subBinY); return true; } bool InternalGuider::processGuiding() { const cproc_out_params *out; uint32_t tick = 0; // On first frame, center the box (reticle) around the star so we do not start with an offset the results in // unnecessary guiding pulses. if (m_isFirstFrame) { if (state == GUIDE_GUIDING) { Vector star_pos = pmath->findLocalStarPosition(); pmath->setReticleParameters(star_pos.x, star_pos.y, -1); } m_isFirstFrame = false; } // calc math. it tracks square - pmath->performProcessing(); + pmath->performProcessing(&guideLog); if (pmath->isStarLost()) m_starLostCounter++; else m_starLostCounter = 0; // do pulse out = pmath->getOutputParameters(); bool sendPulses = true; // If within 95% of max pulse repeatedly, let's abort // if (out->pulse_length[GUIDE_RA] >= (0.95 * Options::rAMaximumPulse()) || // out->pulse_length[GUIDE_DEC] >= (0.95 * Options::dECMaximumPulse())) // { // // Stop sending pulses in case we are guiding and we already sent one high pulse before // // since we do not want to stray too much off the target to purse the guiding star // if (state == GUIDE_GUIDING && m_highPulseCounter > 0) // sendPulses = false; // m_highPulseCounter++; // } // else // m_highPulseCounter=0; double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); if (delta_rms > Options::guideMaxDeltaRMS()) { // Stop sending pulses on the 3rd time the delta RMS is high // so that we don't stray too far off the main target. if (state == GUIDE_GUIDING && m_highRMSCounter > 2) sendPulses = false; m_highRMSCounter++; } else m_highRMSCounter = 0; uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD; uint8_t abortRMSThreshold = (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD; if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold) { qCDebug(KSTARS_EKOS_GUIDE) << "m_starLostCounter" << m_starLostCounter << "m_highRMSCounter" << m_highRMSCounter << "delta_rms" << delta_rms; if (m_starLostCounter > abortStarLostThreshold) emit newLog(i18n("Lost track of the guide star. Searching for guide stars...")); else emit newLog(i18n("Delta RMS threshold value exceeded. Searching for guide stars...")); reacquireTimer.start(); rememberState = state; state = GUIDE_REACQUIRE; emit newStatus(state); return true; } if (sendPulses) { emit newPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC]); // Wait until pulse is over before capturing an image const int waitMS = qMax(out->pulse_length[GUIDE_RA], out->pulse_length[GUIDE_DEC]); // If less than MAX_IMMEDIATE_CAPTURE ms, then capture immediately if (waitMS > MAX_IMMEDIATE_CAPTURE) // Issue frame requests MAX_IMMEDIATE_CAPTURE ms before timeout to account for // propagation delays QTimer::singleShot(waitMS - PROPAGATION_DELAY, [&]() { emit frameCaptureRequested(); }); else emit frameCaptureRequested(); } else emit frameCaptureRequested(); if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) return true; tick = pmath->getTicks(); emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); double raPulse = out->pulse_length[GUIDE_RA]; double dePulse = out->pulse_length[GUIDE_DEC]; //If the pulse was not sent to the mount, it should have 0 value if(out->pulse_dir[GUIDE_RA] == NO_DIR) raPulse = 0; //If the pulse was not sent to the mount, it should have 0 value if(out->pulse_dir[GUIDE_DEC] == NO_DIR) dePulse = 0; //If the pulse was in the Negative direction, it should have a negative sign. if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR) raPulse = -raPulse; //If the pulse was in the Negative direction, it should have a negative sign. if(out->pulse_dir[GUIDE_DEC] == DEC_INC_DIR) dePulse = -dePulse; emit newAxisPulse(raPulse, dePulse); emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); return true; } bool InternalGuider::processImageGuiding() { static int maxPulseCounter = 0; const cproc_out_params *out; uint32_t tick = 0; // calc math. it tracks square pmath->performProcessing(); if (pmath->isStarLost() && ++m_starLostCounter > 2) { emit newLog(i18n("Lost track of phase shift.")); abort(); return false; } else m_starLostCounter = 0; // do pulse out = pmath->getOutputParameters(); // If within 90% of max pulse repeatedly, let's abort if (out->pulse_length[GUIDE_RA] >= (0.9 * Options::rAMaximumPulse()) || out->pulse_length[GUIDE_DEC] >= (0.9 * Options::dECMaximumPulse())) maxPulseCounter++; else maxPulseCounter = 0; if (maxPulseCounter >= 3) { emit newLog(i18n("Lost track of phase shift. Aborting guiding...")); abort(); maxPulseCounter = 0; return false; } emit newPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC]); emit frameCaptureRequested(); if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) return true; tick = pmath->getTicks(); if (tick & 1) { // draw some params in window emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); emit newAxisPulse(out->pulse_length[GUIDE_RA], out->pulse_length[GUIDE_DEC]); emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); } return true; } bool InternalGuider::isImageGuideEnabled() const { return m_ImageGuideEnabled; } void InternalGuider::setImageGuideEnabled(bool value) { m_ImageGuideEnabled = value; pmath->setImageGuideEnabled(value); } void InternalGuider::setRegionAxis(uint32_t value) { pmath->setRegionAxis(value); } QList InternalGuider::getGuideStars() { return pmath->PSFAutoFind(); } bool InternalGuider::selectAutoStar() { FITSData *imageData = guideFrame->getImageData(); if (imageData == nullptr) return false; bool useNativeDetection = false; QList starCenters; if (Options::guideAlgorithm() != SEP_THRESHOLD) starCenters = pmath->PSFAutoFind(); if (starCenters.empty()) { if (Options::guideAlgorithm() == SEP_THRESHOLD) imageData->findStars(ALGORITHM_SEP); else imageData->findStars(); starCenters = imageData->getStarCenters(); if (starCenters.empty()) return false; useNativeDetection = true; // For SEP, prefer flux total if (Options::guideAlgorithm() == SEP_THRESHOLD) std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b) { return a->val > b->val; }); else std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b) { return a->width > b->width; }); guideFrame->setStarsEnabled(true); guideFrame->updateFrame(); } int maxX = imageData->width(); int maxY = imageData->height(); int scores[MAX_GUIDE_STARS]; int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count(); for (int i = 0; i < maxIndex; i++) { int score = 100; Edge *center = starCenters.at(i); if (useNativeDetection) { // Severely reject stars close to edges if (center->x < (center->width * 5) || center->y < (center->width * 5) || center->x > (maxX - center->width * 5) || center->y > (maxY - center->width * 5)) score -= 1000; // Reject stars bigger than square if (center->width > float(guideBoxSize) / subBinX) score -= 1000; else { if (Options::guideAlgorithm() == SEP_THRESHOLD) score += sqrt(center->val); else // Moderately favor brighter stars score += center->width * center->width; } // Moderately reject stars close to other stars foreach (Edge *edge, starCenters) { if (edge == center) continue; if (fabs(center->x - edge->x) < center->width * 2 && fabs(center->y - edge->y) < center->width * 2) { score -= 15; break; } } } else { score = center->val; } scores[i] = score; } int maxScore = -1; int maxScoreIndex = -1; for (int i = 0; i < maxIndex; i++) { if (scores[i] > maxScore) { maxScore = scores[i]; maxScoreIndex = i; } } if (maxScoreIndex < 0) { qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected."; return false; } /*if (ui.autoSquareSizeCheck->isEnabled() && ui.autoSquareSizeCheck->isChecked()) { // Select appropriate square size int idealSize = ceil(starCenters[maxScoreIndex]->width * 1.5); if (Options::guideLogging()) qDebug() << "Guide: Ideal calibration box size for star width: " << starCenters[maxScoreIndex]->width << " is " << idealSize << " pixels"; // TODO Set square size in GuideModule }*/ QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0); if (useNativeDetection == false) qDeleteAll(starCenters); emit newStarPosition(newStarCenter, true); return true; } bool InternalGuider::reacquire() { bool rc = selectAutoStar(); if (rc) { m_highRMSCounter = m_starLostCounter = 0; m_isFirstFrame = true; pmath->reset(); // If we were in the process of dithering, wait until settle and resume if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) { if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; + guideLog.settleStartedInfo(); emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); } else { state = GUIDE_GUIDING; emit newStatus(state); } } else if (reacquireTimer.elapsed() > static_cast(Options::guideLostStarTimeout() * 1000)) { emit newLog(i18n("Failed to find any suitable guide stars. Aborting...")); abort(); return false; } emit frameCaptureRequested(); return rc; } +void InternalGuider::fillGuideInfo(GuideLog::GuideInfo *info) +{ + // NOTE: just using the X values, phd2logview assumes x & y the same. + // pixel scale in arc-sec / pixel. The 2nd and 3rd values seem redundent, but are + // in the phd2 logs. + info->pixelScale = this->ccdPixelSizeX * this->subBinX * this->mountFocalLength / 206.264; + info->binning = this->subBinX; + info->focalLength = this->mountFocalLength; + info->ra = this->mountRA.Degrees(); + info->dec = this->mountDEC.Degrees(); + info->azimuth = this->mountAzimuth.Degrees(); + info->altitude = this->mountAltitude.Degrees(); + info->pierSide = this->pierSide; +} + } diff --git a/kstars/ekos/guide/internalguide/internalguider.h b/kstars/ekos/guide/internalguide/internalguider.h index 6b524d56a..18b20d547 100644 --- a/kstars/ekos/guide/internalguide/internalguider.h +++ b/kstars/ekos/guide/internalguide/internalguider.h @@ -1,242 +1,247 @@ /* Ekos Internal Guider Class Copyright (C) 2016 Jasem Mutlaq . Based on lin_guider This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "matr.h" #include "indi/indicommon.h" #include "../guideinterface.h" +#include "guidelog.h" #include #include #include #include #include class QVector3D; class cgmath; class FITSView; class Edge; namespace Ekos { class InternalGuider : public GuideInterface { Q_OBJECT public: enum CalibrationStage { CAL_IDLE, CAL_ERROR, CAL_CAPTURE_IMAGE, CAL_SELECT_STAR, CAL_START, CAL_RA_INC, CAL_RA_DEC, CAL_DEC_INC, CAL_DEC_DEC }; enum CalibrationType { CAL_NONE, CAL_RA_AUTO, CAL_RA_DEC_AUTO }; InternalGuider(); bool Connect() override { return true; } bool Disconnect() override { return true; } bool isConnected() override { return true; } bool calibrate() override; bool guide() override; bool abort() override; bool suspend() override; bool resume() override; bool dither(double pixels) override; bool ditherXY(double x, double y); bool clearCalibration() override { return true; } bool reacquire() override; bool setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY) override; bool setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength) override; // Set Star Position void setStarPosition(QVector3D &starCenter) override; // Select algorithm void setSquareAlgorithm(int index); // Reticle Parameters void setReticleParameters(double x, double y, double angle); bool getReticleParameters(double *x, double *y, double *angle); // Guide Square Box Size void setGuideBoxSize(uint32_t value) { guideBoxSize = value; } // Guide View void setGuideView(FITSView *guideView); // Region Axis void setRegionAxis(uint32_t value); bool start(); bool isGuiding(void) const; void setAO(bool enable); void setInterface(void); bool isRapidGuide() { return m_UseRapidGuide; } double getAOLimit(); void setSubFramed(bool enable) { m_isSubFramed = enable; } void setGuideOptions(const QString &algorithm, bool useSubFrame, bool useRapidGuide); QString getAlgorithm(); bool useSubFrame(); bool useRapidGuide(); bool isImageGuideEnabled() const; void setImageGuideEnabled(bool value); QList getGuideStars(); // Select a guide star automatically bool selectAutoStar(); // Manual Dither bool processManualDithering(); public slots: void setDECSwap(bool enable); protected slots: void trackingStarSelected(int x, int y); void setDitherSettled(); signals: void newPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs); void newPulse(GuideDirection dir, int msecs); //void newStarPosition(QVector3D, bool); void DESwapChanged(bool enable); private: // Calibration void calibrateRADECRecticle(bool ra_only); // 1 or 2-axis calibration void processCalibration(); // Guiding bool processGuiding(); // Image Guiding bool processImageGuiding(); void reset(); + // Logging + void fillGuideInfo(GuideLog::GuideInfo *info); + std::unique_ptr pmath; QPointer guideFrame; bool m_isStarted { false }; bool m_isSubFramed { false }; bool m_isFirstFrame { false }; bool m_UseRapidGuide { false }; bool m_ImageGuideEnabled { false }; int m_starLostCounter { 0 }; QFile logFile; uint32_t guideBoxSize { 32 }; struct { int auto_drift_time { 5 }; int turn_back_time { 0 }; int ra_iterations { 0 }; int dec_iterations { 0 }; int last_pulse { 0 }; int ra_total_pulse { 0 }; int de_total_pulse { 0 }; double phi { 0 }; uint8_t backlash { 0 }; } m_CalibrationParams; struct { double start_x1 { 0 }; double start_y1 { 0 }; double end_x1 { 0 }; double end_y1 { 0 }; double start_x2 { 0 }; double start_y2 { 0 }; double end_x2 { 0 }; double end_y2 { 0 }; double last_x { 0 }; double last_y { 0 }; double ra_distance {0}; double de_distance {0}; } m_CalibrationCoords; Vector m_DitherTargetPosition; uint8_t m_DitherRetries {0}; QTime reacquireTimer; int m_highRMSCounter {0}; Ekos::Matrix ROT_Z; CalibrationStage calibrationStage { CAL_IDLE }; CalibrationType calibrationType; Ekos::GuideState rememberState { GUIDE_IDLE }; // Progressive Manual Dither QQueue m_ProgressiveDither; // How many high RMS pulses before we stop static const uint8_t MAX_RMS_THRESHOLD = 5; // How many lost stars before we stop static const uint8_t MAX_LOST_STAR_THRESHOLD = 5; // Maximum pulse time limit for immediate capture. Any pulses longer that this // will be delayed until pulse is over static const uint16_t MAX_IMMEDIATE_CAPTURE = 250; // When to start capture before pulse delay is over static const uint16_t PROPAGATION_DELAY = 100; // How many 'random' pixels can we move before we have to force direction reversal? static const uint8_t MAX_DITHER_TRAVEL = 15; QPair accumulator; + GuideLog guideLog; }; } diff --git a/kstars/ekos/guide/opsguide.ui b/kstars/ekos/guide/opsguide.ui index 83cc4c4f0..c6f4e6c40 100644 --- a/kstars/ekos/guide/opsguide.ui +++ b/kstars/ekos/guide/opsguide.ui @@ -1,484 +1,497 @@ OpsGuide 0 0 299 500 3 3 3 3 3 Settings 3 3 3 3 3 Algorithm 0.500000000000000 30.000000000000000 10.000000000000000 Maximum delta RMS permitted before stopping guide process and searching for new guide stars. Max Delta RMS <html><head/><body><p>If star tracking is lost due to passing clouds or other reasons, wait this many seconds before giving up.</p></body></html> Lost Star timeout false Rapid Guide + + + + <html><head/><body><p>If checked and the internal guider is run, a log file is saved in the default logging directory. This is not a debug log, it is meant for user's to improve logging and can be viewed with phd2logview.</p></body></html> + + + true + + + Save Internal Guider User Log + + + 0 0 Smart SEP Fast Auto Threshold No Threshold seconds 3600 10 60 arcsecs Dither 3 Number of pixels to move the guiding square in a random direction. 0.100000000000000 10.000000000000000 0.100000000000000 2.000000000000000 After dither is successful, wait for this many seconds before proceeding. Settle seconds Threshold time limit for successful dither settling 360 Move locked guiding square location after frame capture Dither pixels Maximum allowable distance for guiding to be considered settled. 50.000000000000000 seconds pixels Timeout After dither is successful, wait for this many seconds before proceeding. 360 Dither after this many captured frames in the CCD module 1 60 frames Dither after this many captured frames in the CCD module Frequency <html><head/><body><p>Maximum dithering iteration attempts before giving up</p></body></html> Max. Iterations <html><head/><body><p>Maximum dithering iteration attempts before giving up</p></body></html> 1 30 <html><head/><body><p>If checked, autoguiding is aborted when dithering fails. Otherwise, guiding resumes normally.</p></body></html> Abort Autoguide on failure <html><head/><body><p>Perform dithering when not guiding.</p></body></html> Non-Guide Dither Pulse Pulse length in milliseconds to command mount motion in a random direction 10 10000 100 ms false Image Guiding Use Image Guiding Region Axis: 64 128 256 512 1024 Qt::Horizontal 40 20 Qt::Vertical 20 40 diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp index a8e244ecb..767f861d2 100644 --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -1,3562 +1,3565 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "manager.h" #include "ekosadaptor.h" #include "kstars.h" #include "kstarsdata.h" #include "opsekos.h" #include "Options.h" #include "profileeditor.h" #include "profilewizard.h" #include "indihub.h" #include "skymap.h" #include "auxiliary/darklibrary.h" #include "auxiliary/QProgressIndicator.h" #include "auxiliary/ksmessagebox.h" #include "capture/sequencejob.h" #include "fitsviewer/fitstab.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsdata.h" #include "indi/clientmanager.h" #include "indi/driverinfo.h" #include "indi/drivermanager.h" #include "indi/guimanager.h" #include "indi/indielement.h" #include "indi/indilistener.h" #include "indi/indiproperty.h" #include "indi/indiwebmanager.h" #include "ekoslive/ekosliveclient.h" #include "ekoslive/message.h" #include "ekoslive/media.h" #include #include #include #include #include #include #include #include #define MAX_REMOTE_INDI_TIMEOUT 15000 #define MAX_LOCAL_INDI_TIMEOUT 5000 namespace Ekos { Manager *Manager::_Manager = nullptr; Manager *Manager::Instance() { if (_Manager == nullptr) _Manager = new Manager(Options::independentWindowEkos() ? nullptr : KStars::Instance()); return _Manager; } void Manager::release() { delete _Manager; } Manager::Manager(QWidget * parent) : QDialog(parent) { #ifdef Q_OS_OSX if (Options::independentWindowEkos()) setWindowFlags(Qt::Window); else { setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(changeAlwaysOnTop(Qt::ApplicationState))); } #else // if (Options::independentWindowEkos()) // setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); #endif setupUi(this); qRegisterMetaType("Ekos::CommunicationStatus"); qDBusRegisterMetaType(); new EkosAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos", this); setWindowIcon(QIcon::fromTheme("kstars_ekos")); profileModel.reset(new QStandardItemModel(0, 4)); profileModel->setHorizontalHeaderLabels(QStringList() << "id" << "name" << "host" << "port"); captureProgress->setValue(0); sequenceProgress->setValue(0); sequenceProgress->setDecimals(0); sequenceProgress->setFormat("%v"); imageProgress->setValue(0); imageProgress->setDecimals(1); imageProgress->setFormat("%v"); imageProgress->setBarStyle(QRoundProgressBar::StyleLine); countdownTimer.setInterval(1000); connect(&countdownTimer, &QTimer::timeout, this, &Ekos::Manager::updateCaptureCountDown); toolsWidget->setIconSize(QSize(48, 48)); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); // Enable scheduler Tab toolsWidget->setTabEnabled(1, false); // Start/Stop INDI Server connect(processINDIB, &QPushButton::clicked, this, &Ekos::Manager::processINDI); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); // Connect/Disconnect INDI devices connect(connectB, &QPushButton::clicked, this, &Ekos::Manager::connectDevices); connect(disconnectB, &QPushButton::clicked, this, &Ekos::Manager::disconnectDevices); ekosLiveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); ekosLiveClient.reset(new EkosLive::Client(this)); connect(ekosLiveClient.get(), &EkosLive::Client::connected, [this]() { emit ekosLiveStatusChanged(true); }); connect(ekosLiveClient.get(), &EkosLive::Client::disconnected, [this]() { emit ekosLiveStatusChanged(false); }); // INDI Control Panel //connect(controlPanelB, &QPushButton::clicked, GUIManager::Instance(), SLOT(show())); connect(ekosLiveB, &QPushButton::clicked, [&]() { ekosLiveClient.get()->show(); ekosLiveClient.get()->raise(); }); connect(this, &Manager::ekosStatusChanged, ekosLiveClient.get()->message(), &EkosLive::Message::setEkosStatingStatus); connect(ekosLiveClient.get()->message(), &EkosLive::Message::connected, [&]() { ekosLiveB->setIcon(QIcon(":/icons/cloud-online.svg")); }); connect(ekosLiveClient.get()->message(), &EkosLive::Message::disconnected, [&]() { ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud")); }); connect(ekosLiveClient.get()->media(), &EkosLive::Media::newBoundingRect, ekosLiveClient.get()->message(), &EkosLive::Message::setBoundingRect); connect(ekosLiveClient.get()->message(), &EkosLive::Message::resetPolarView, ekosLiveClient.get()->media(), &EkosLive::Media::resetPolarView); connect(ekosLiveClient.get()->message(), &EkosLive::Message::previewJPEGGenerated, ekosLiveClient.get()->media(), &EkosLive::Media::sendPreviewJPEG); connect(KSMessageBox::Instance(), &KSMessageBox::newMessage, ekosLiveClient.get()->message(), &EkosLive::Message::sendDialog); // Serial Port Assistant connect(serialPortAssistantB, &QPushButton::clicked, [&]() { serialPortAssistant->show(); serialPortAssistant->raise(); }); connect(this, &Ekos::Manager::ekosStatusChanged, [&](Ekos::CommunicationStatus status) { indiControlPanelB->setEnabled(status == Ekos::Success); connectB->setEnabled(false); disconnectB->setEnabled(false); profileGroup->setEnabled(status == Ekos::Idle || status == Ekos::Error); m_isStarted = (status == Ekos::Success || status == Ekos::Pending); if (status == Ekos::Success) { processINDIB->setIcon(QIcon::fromTheme("media-playback-stop")); processINDIB->setToolTip(i18n("Stop")); setWindowTitle(i18n("Ekos - %1 Profile", currentProfile->name)); } else if (status == Ekos::Error || status == Ekos::Idle) { processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); } else { processINDIB->setIcon(QIcon::fromTheme("call-stop")); processINDIB->setToolTip(i18n("Connection in progress. Click to abort.")); } }); connect(indiControlPanelB, &QPushButton::clicked, [&]() { KStars::Instance()->actionCollection()->action("show_control_panel")->trigger(); }); connect(optionsB, &QPushButton::clicked, [&]() { KStars::Instance()->actionCollection()->action("configure")->trigger(); }); // Save as above, but it appears in all modules connect(ekosOptionsB, &QPushButton::clicked, this, &Ekos::Manager::showEkosOptions); // Clear Ekos Log connect(clearB, &QPushButton::clicked, this, &Ekos::Manager::clearLog); // Logs KConfigDialog * dialog = new KConfigDialog(this, "logssettings", Options::self()); opsLogs = new Ekos::OpsLogs(); KPageWidgetItem * page = dialog->addPage(opsLogs, i18n("Logging")); page->setIcon(QIcon::fromTheme("configure")); connect(logsB, &QPushButton::clicked, dialog, &KConfigDialog::show); connect(dialog->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); connect(dialog->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); // Summary // previewPixmap = new QPixmap(QPixmap(":/images/noimage.png")); // Profiles connect(addProfileB, &QPushButton::clicked, this, &Ekos::Manager::addProfile); connect(editProfileB, &QPushButton::clicked, this, &Ekos::Manager::editProfile); connect(deleteProfileB, &QPushButton::clicked, this, &Ekos::Manager::deleteProfile); connect(profileCombo, static_cast(&QComboBox::currentTextChanged), [ = ](const QString & text) { Options::setProfile(text); if (text == "Simulators") { editProfileB->setEnabled(false); deleteProfileB->setEnabled(false); } else { editProfileB->setEnabled(true); deleteProfileB->setEnabled(true); } }); // Ekos Wizard connect(wizardProfileB, &QPushButton::clicked, this, &Ekos::Manager::wizardProfile); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Set Profile icons addProfileB->setIcon(QIcon::fromTheme("list-add")); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setIcon(QIcon::fromTheme("document-edit")); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setIcon(QIcon::fromTheme("list-remove")); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); wizardProfileB->setIcon(QIcon::fromTheme("tools-wizard")); wizardProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); customDriversB->setIcon(QIcon::fromTheme("roll")); customDriversB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(customDriversB, &QPushButton::clicked, DriverManager::Instance(), &DriverManager::showCustomDrivers); // Load all drivers loadDrivers(); // Load add driver profiles loadProfiles(); // INDI Control Panel and Ekos Options optionsB->setIcon(QIcon::fromTheme("configure", QIcon(":/icons/ekos_setup.png"))); optionsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Setup Tab toolsWidget->tabBar()->setTabIcon(0, QIcon(":/icons/ekos_setup.png")); toolsWidget->tabBar()->setTabToolTip(0, i18n("Setup")); // Initialize Ekos Scheduler Module schedulerProcess.reset(new Ekos::Scheduler()); toolsWidget->addTab(schedulerProcess.get(), QIcon(":/icons/ekos_scheduler.png"), ""); toolsWidget->tabBar()->setTabToolTip(1, i18n("Scheduler")); connect(schedulerProcess.get(), &Scheduler::newLog, this, &Ekos::Manager::updateLog); //connect(schedulerProcess.get(), SIGNAL(newTarget(QString)), mountTarget, SLOT(setText(QString))); connect(schedulerProcess.get(), &Ekos::Scheduler::newTarget, [&](const QString & target) { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); // Temporary fix. Not sure how to resize Ekos Dialog to fit contents of the various tabs in the QScrollArea which are added // dynamically. I used setMinimumSize() but it doesn't appear to make any difference. // Also set Layout policy to SetMinAndMaxSize as well. Any idea how to fix this? // FIXME //resize(1000,750); summaryPreview.reset(new FITSView(previewWidget, FITS_NORMAL)); previewWidget->setContentsMargins(0, 0, 0, 0); summaryPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); summaryPreview->setBaseSize(previewWidget->size()); summaryPreview->createFloatingToolBar(); summaryPreview->setCursorMode(FITSView::dragCursor); QVBoxLayout * vlayout = new QVBoxLayout(); vlayout->addWidget(summaryPreview.get()); previewWidget->setLayout(vlayout); // JM 2019-01-19: Why cloud images depend on summary preview? // connect(summaryPreview.get(), &FITSView::loaded, [&]() // { // // UUID binds the cloud & preview frames by a common key // QString uuid = QUuid::createUuid().toString(); // uuid = uuid.remove(QRegularExpression("[-{}]")); // //ekosLiveClient.get()->media()->sendPreviewImage(summaryPreview.get(), uuid); // ekosLiveClient.get()->cloud()->sendPreviewImage(summaryPreview.get(), uuid); // }); if (Options::ekosLeftIcons()) { toolsWidget->setTabPosition(QTabWidget::West); QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(0); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(0, icon); icon = toolsWidget->tabIcon(1); pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(1, icon); } //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); resize(Options::ekosWindowWidth(), Options::ekosWindowHeight()); } void Manager::changeAlwaysOnTop(Qt::ApplicationState state) { if (isVisible()) { if (state == Qt::ApplicationActive) setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); else setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); } } Manager::~Manager() { toolsWidget->disconnect(this); } void Manager::closeEvent(QCloseEvent * event) { // QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); // a->setChecked(false); // 2019-02-14 JM: Close event, for some reason, make all the children disappear // when the widget is shown again. Applying a workaround here event->ignore(); hide(); } void Manager::hideEvent(QHideEvent * /*event*/) { Options::setEkosWindowWidth(width()); Options::setEkosWindowHeight(height()); QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); } void Manager::showEvent(QShowEvent * /*event*/) { QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(true); // Just show the profile wizard ONCE per session if (profileWizardLaunched == false && profiles.count() == 1) { profileWizardLaunched = true; wizardProfile(); } } void Manager::resizeEvent(QResizeEvent *) { //previewImage->setPixmap(previewPixmap->scaled(previewImage->width(), previewImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (focusStarPixmap.get() != nullptr) focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (focusProfilePixmap) //focusProfileImage->setPixmap(focusProfilePixmap->scaled(focusProfileImage->width(), focusProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (guideStarPixmap.get() != nullptr) guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (guideProfilePixmap) //guideProfileImage->setPixmap(guideProfilePixmap->scaled(guideProfileImage->width(), guideProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::loadProfiles() { profiles.clear(); KStarsData::Instance()->userdb()->GetAllProfiles(profiles); profileModel->clear(); for (auto &pi : profiles) { QList info; info << new QStandardItem(pi->id) << new QStandardItem(pi->name) << new QStandardItem(pi->host) << new QStandardItem(pi->port); profileModel->appendRow(info); } profileModel->sort(0); profileCombo->blockSignals(true); profileCombo->setModel(profileModel.get()); profileCombo->setModelColumn(1); profileCombo->blockSignals(false); // Load last used profile from options int index = profileCombo->findText(Options::profile()); // If not found, set it to first item if (index == -1) index = 0; profileCombo->setCurrentIndex(index); } void Manager::loadDrivers() { foreach (DriverInfo * dv, DriverManager::Instance()->getDrivers()) { if (dv->getDriverSource() != HOST_SOURCE) driversList[dv->getLabel()] = dv; } } void Manager::reset() { qCDebug(KSTARS_EKOS) << "Resetting Ekos Manager..."; // Filter Manager filterManager.reset(new Ekos::FilterManager()); nDevices = 0; useGuideHead = false; useST4 = false; removeTabs(); genericDevices.clear(); managedDevices.clear(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); domeProcess.reset(); alignProcess.reset(); mountProcess.reset(); weatherProcess.reset(); observatoryProcess.reset(); dustCapProcess.reset(); DarkLibrary::Instance()->reset(); Ekos::CommunicationStatus previousStatus = m_ekosStatus; m_ekosStatus = Ekos::Idle; if (previousStatus != m_ekosStatus) emit ekosStatusChanged(m_ekosStatus); previousStatus = m_indiStatus; m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); processINDIB->setEnabled(true); mountGroup->setEnabled(false); focusGroup->setEnabled(false); captureGroup->setEnabled(false); guideGroup->setEnabled(false); sequenceLabel->setText(i18n("Sequence")); sequenceProgress->setValue(0); captureProgress->setValue(0); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); imageRemainingTime->setText("--:--:--"); mountStatus->setText(i18n("Idle")); mountStatus->setStyleSheet(QString()); captureStatus->setText(i18n("Idle")); focusStatus->setText(i18n("Idle")); guideStatus->setText(i18n("Idle")); if (capturePI) capturePI->stopAnimation(); if (mountPI) mountPI->stopAnimation(); if (focusPI) focusPI->stopAnimation(); if (guidePI) guidePI->stopAnimation(); m_isStarted = false; processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); } void Manager::processINDI() { if (m_isStarted == false) start(); else stop(); } void Manager::stop() { cleanDevices(); serialPortAssistant.reset(); serialPortAssistantB->setEnabled(false); if (indiHubAgent) indiHubAgent->terminate(); profileGroup->setEnabled(true); setWindowTitle(i18n("Ekos")); } void Manager::start() { // Don't start if it is already started before if (m_ekosStatus == Ekos::Pending || m_ekosStatus == Ekos::Success) { qCWarning(KSTARS_EKOS) << "Ekos Manager start called but current Ekos Status is" << m_ekosStatus << "Ignoring request."; return; } if (m_LocalMode) qDeleteAll(managedDrivers); managedDrivers.clear(); // If clock was paused, unpaused it and sync time if (KStarsData::Instance()->clock()->isActive() == false) { KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); KStarsData::Instance()->clock()->start(); } // Reset Ekos Manager reset(); // Get Current Profile currentProfile = getCurrentProfile(); m_LocalMode = currentProfile->isLocal(); // Load profile location if one exists updateProfileLocation(currentProfile); bool haveCCD = false, haveGuider = false; // If external guide is specified in the profile, set the // corresponding options if (currentProfile->guidertype == Ekos::Guide::GUIDE_PHD2) { Options::setPHD2Host(currentProfile->guiderhost); Options::setPHD2Port(currentProfile->guiderport); } else if (currentProfile->guidertype == Ekos::Guide::GUIDE_LINGUIDER) { Options::setLinGuiderHost(currentProfile->guiderhost); Options::setLinGuiderPort(currentProfile->guiderport); } // For locally running INDI server if (m_LocalMode) { DriverInfo * drv = driversList.value(currentProfile->mount()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->ccd()); if (drv != nullptr) { managedDrivers.append(drv->clone()); haveCCD = true; } Options::setGuiderType(currentProfile->guidertype); drv = driversList.value(currentProfile->guider()); if (drv != nullptr) { haveGuider = true; // If the guider and ccd are the same driver, we have two cases: // #1 Drivers that only support ONE device per driver (such as sbig) // #2 Drivers that supports multiples devices per driver (such as sx) // For #1, we modify guider_di to make a unique label for the other device with postfix "Guide" // For #2, we set guider_di to nullptr and we prompt the user to select which device is primary ccd and which is guider // since this is the only way to find out in real time. if (haveCCD && currentProfile->guider() == currentProfile->ccd()) { if (checkUniqueBinaryDriver( driversList.value(currentProfile->ccd()), drv)) { drv = nullptr; } else { drv->setUniqueLabel(drv->getLabel() + " Guide"); } } if (drv) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->ao()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->filter()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->focuser()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->dome()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->weather()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux1()); if (drv != nullptr) { if (!checkUniqueBinaryDriver(driversList.value(currentProfile->ccd()), drv) && !checkUniqueBinaryDriver(driversList.value(currentProfile->guider()), drv)) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->aux2()); if (drv != nullptr) { if (!checkUniqueBinaryDriver(driversList.value(currentProfile->ccd()), drv) && !checkUniqueBinaryDriver(driversList.value(currentProfile->guider()), drv)) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->aux3()); if (drv != nullptr) { if (!checkUniqueBinaryDriver(driversList.value(currentProfile->ccd()), drv) && !checkUniqueBinaryDriver(driversList.value(currentProfile->guider()), drv)) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->aux4()); if (drv != nullptr) { if (!checkUniqueBinaryDriver(driversList.value(currentProfile->ccd()), drv) && !checkUniqueBinaryDriver(driversList.value(currentProfile->guider()), drv)) managedDrivers.append(drv->clone()); } // Add remote drivers if we have any if (currentProfile->remotedrivers.isEmpty() == false && currentProfile->remotedrivers.contains("@")) { for (auto remoteDriver : currentProfile->remotedrivers.split(",")) { QString name, label, host("localhost"), port("7624"); QStringList properties = remoteDriver.split(QRegExp("[@:]")); if (properties.length() > 1) { name = properties[0]; host = properties[1]; if (properties.length() > 2) port = properties[2]; } DriverInfo * dv = new DriverInfo(name); dv->setRemoteHost(host); dv->setRemotePort(port); label = name; // Remove extra quotes label.remove("\""); dv->setLabel(label); dv->setUniqueLabel(label); managedDrivers.append(dv); } } if (haveCCD == false && haveGuider == false) { KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate.")); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); return; } nDevices = managedDrivers.count(); } else { DriverInfo * remote_indi = new DriverInfo(QString("Ekos Remote Host")); remote_indi->setHostParameters(currentProfile->host, QString::number(currentProfile->port)); remote_indi->setDriverSource(GENERATED_SOURCE); managedDrivers.append(remote_indi); haveCCD = currentProfile->drivers.contains("CCD"); haveGuider = currentProfile->drivers.contains("Guider"); Options::setGuiderType(currentProfile->guidertype); if (haveCCD == false && haveGuider == false) { KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate.")); delete (remote_indi); nDevices = 0; m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); return; } nDevices = currentProfile->drivers.count(); } connect(INDIListener::Instance(), &INDIListener::newDevice, this, &Ekos::Manager::processNewDevice); connect(INDIListener::Instance(), &INDIListener::newTelescope, this, &Ekos::Manager::setTelescope); connect(INDIListener::Instance(), &INDIListener::newCCD, this, &Ekos::Manager::setCCD); connect(INDIListener::Instance(), &INDIListener::newFilter, this, &Ekos::Manager::setFilter); connect(INDIListener::Instance(), &INDIListener::newFocuser, this, &Ekos::Manager::setFocuser); connect(INDIListener::Instance(), &INDIListener::newDome, this, &Ekos::Manager::setDome); connect(INDIListener::Instance(), &INDIListener::newWeather, this, &Ekos::Manager::setWeather); connect(INDIListener::Instance(), &INDIListener::newDustCap, this, &Ekos::Manager::setDustCap); connect(INDIListener::Instance(), &INDIListener::newLightBox, this, &Ekos::Manager::setLightBox); connect(INDIListener::Instance(), &INDIListener::newST4, this, &Ekos::Manager::setST4); connect(INDIListener::Instance(), &INDIListener::deviceRemoved, this, &Ekos::Manager::removeDevice, Qt::DirectConnection); #ifdef Q_OS_OSX if (m_LocalMode || currentProfile->host == "localhost") { if (isRunning("PTPCamera")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(nullptr, i18n("Ekos detected that PTP Camera is running and may prevent a Canon or Nikon camera from connecting to Ekos. Do you want to quit PTP Camera now?"), i18n("PTP Camera"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_PTPCamera"))) { //TODO is there a better way to do this. QProcess p; p.start("killall PTPCamera"); p.waitForFinished(); } } } #endif if (m_LocalMode) { auto executeStartINDIServices = [this]() { appendLogText(i18n("Starting INDI services...")); connect(DriverManager::Instance(), &DriverManager::serverStarted, this, &Manager::processServerStarted, Qt::UniqueConnection); connect(DriverManager::Instance(), &DriverManager::serverTerminated, this, &Manager::processServerTerminated, Qt::UniqueConnection); if (DriverManager::Instance()->startDevices(managedDrivers) == false) { INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); } m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); if (currentProfile->autoConnect) appendLogText(i18n("INDI services started on port %1.", managedDrivers.first()->getPort())); else appendLogText( i18n("INDI services started on port %1. Please connect devices.", managedDrivers.first()->getPort())); QTimer::singleShot(MAX_LOCAL_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); }; // If INDI server is already running, let's see if we need to shut it down first if (isRunning("indiserver")) { connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeStartINDIServices]() { KSMessageBox::Instance()->disconnect(this); DriverManager::Instance()->stopAllDevices(); //TODO is there a better way to do this. QProcess p; p.start("pkill indiserver"); p.waitForFinished(); QTimer::singleShot(500, executeStartINDIServices); }); connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [this, executeStartINDIServices]() { KSMessageBox::Instance()->disconnect(this); executeStartINDIServices(); }); KSMessageBox::Instance()->questionYesNo(i18n("Ekos detected an instance of INDI server running. Do you wish to " "shut down the existing instance before starting a new one?"), i18n("INDI Server"), 5); } else executeStartINDIServices(); } else { auto runConnection = [this]() { // If it got cancelled by the user, return immediately. if (m_ekosStatus != Ekos::Pending) return; appendLogText( i18n("Connecting to remote INDI server at %1 on port %2 ...", currentProfile->host, currentProfile->port)); if (DriverManager::Instance()->connectRemoteHost(managedDrivers.first())) { connect(DriverManager::Instance(), &DriverManager::serverTerminated, this, &Manager::processServerTerminated, Qt::UniqueConnection); appendLogText( i18n("INDI services started. Connection to remote INDI server is successful. Waiting for devices...")); QTimer::singleShot(MAX_REMOTE_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); } else { appendLogText(i18n("Failed to connect to remote INDI server.")); INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); return; } }; auto runProfile = [this, runConnection]() { // If it got cancelled by the user, return immediately. if (m_ekosStatus != Ekos::Pending) return; INDI::WebManager::syncCustomDrivers(currentProfile); currentProfile->isStellarMate = INDI::WebManager::isStellarMate(currentProfile); if (INDI::WebManager::areDriversRunning(currentProfile) == false) { INDI::WebManager::stopProfile(currentProfile); if (INDI::WebManager::startProfile(currentProfile) == false) { appendLogText(i18n("Failed to start profile on remote INDI Web Manager.")); return; } appendLogText(i18n("Starting profile on remote INDI Web Manager...")); m_RemoteManagerStart = true; } runConnection(); }; m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); // If we need to use INDI Web Manager if (currentProfile->INDIWebManagerPort > 0) { appendLogText(i18n("Establishing communication with remote INDI Web Manager...")); m_RemoteManagerStart = false; QFutureWatcher *watcher = new QFutureWatcher(); connect(watcher, &QFutureWatcher::finished, [this, runConnection, runProfile, watcher]() { watcher->deleteLater(); // If it got cancelled by the user, return immediately. if (m_ekosStatus != Ekos::Pending) return; // If web manager is online, try to run the profile in it if (watcher->result()) { runProfile(); } // Else, try to connect directly to INDI server as there could be a chance // that it is already running. else { appendLogText(i18n("Warning: INDI Web Manager is not online.")); runConnection(); } }); QFuture result = INDI::AsyncWebManager::isOnline(currentProfile); watcher->setFuture(result); } else { runConnection(); } } } void Manager::checkINDITimeout() { // Don't check anything unless we're still pending if (m_ekosStatus != Ekos::Pending) return; if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); return; } if (m_LocalMode) { QStringList remainingDevices; foreach (DriverInfo * drv, managedDrivers) { if (drv->getDevices().count() == 0) remainingDevices << QString("+ %1").arg( drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName()); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish:\n%1\nPlease ensure the device is connected and powered on.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish the following devices:\n%1\nPlease ensure each device is connected " "and powered on.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } else { QStringList remainingDevices; for (auto &driver : currentProfile->drivers.values()) { bool driverFound = false; for (auto &device : genericDevices) { if (device->getBaseDevice()->getDriverName() == driver) { driverFound = true; break; } } if (driverFound == false) remainingDevices << QString("+ %1").arg(driver); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish remote device:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish remote devices:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } m_ekosStatus = Ekos::Error; } void Manager::connectDevices() { // Check if already connected int nConnected = 0; Ekos::CommunicationStatus previousStatus = m_indiStatus; for (auto &device : genericDevices) { if (device->isConnected()) nConnected++; } if (genericDevices.count() == nConnected) { m_indiStatus = Ekos::Success; emit indiStatusChanged(m_indiStatus); return; } m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName(); device->Connect(); } connectB->setEnabled(false); disconnectB->setEnabled(true); appendLogText(i18n("Connecting INDI devices...")); } void Manager::disconnectDevices() { for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName(); device->Disconnect(); } appendLogText(i18n("Disconnecting INDI devices...")); } void Manager::processServerTerminated(const QString &host, const QString &port) { if ((m_LocalMode && managedDrivers.first()->getPort() == port) || (currentProfile->host == host && currentProfile->port == port.toInt())) { cleanDevices(false); if (indiHubAgent) indiHubAgent->terminate(); } } void Manager::processServerStarted(const QString &host, const QString &port) { if (m_LocalMode && currentProfile->indihub != INDIHub::None) { if (QFile(Options::iNDIHubAgent()).exists()) { indiHubAgent = new QProcess(); QStringList args; args << "--indi-server" << QString("%1:%2").arg(host).arg(port); if (currentProfile->guidertype == Ekos::Guide::GUIDE_PHD2) args << "--phd2-server" << QString("%1:%2").arg(currentProfile->guiderhost).arg(currentProfile->guiderport); args << "--mode" << INDIHub::toString(currentProfile->indihub); indiHubAgent->start(Options::iNDIHubAgent(), args); qCDebug(KSTARS_EKOS) << "Started INDIHub agent."; } } } void Manager::cleanDevices(bool stopDrivers) { if (m_ekosStatus == Ekos::Idle) return; INDIListener::Instance()->disconnect(this); DriverManager::Instance()->disconnect(this); if (managedDrivers.isEmpty() == false) { if (m_LocalMode) { if (stopDrivers) DriverManager::Instance()->stopDevices(managedDrivers); } else { if (stopDrivers) DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first()); if (m_RemoteManagerStart && currentProfile->INDIWebManagerPort != -1) { INDI::WebManager::stopProfile(currentProfile); m_RemoteManagerStart = false; } } } reset(); profileGroup->setEnabled(true); appendLogText(i18n("INDI services stopped.")); } void Manager::processNewDevice(ISD::GDInterface * devInterface) { qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << devInterface->getDeviceName(); Ekos::CommunicationStatus previousStatus = m_indiStatus; for(auto &device : genericDevices) { if (device->getDeviceName() == devInterface->getDeviceName()) { qCWarning(KSTARS_EKOS) << "Found duplicate device, ignoring..."; return; } } // Always reset INDI Connection status if we receive a new device m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); genericDevices.append(devInterface); nDevices--; connect(devInterface, &ISD::GDInterface::Connected, this, &Ekos::Manager::deviceConnected); connect(devInterface, &ISD::GDInterface::Disconnected, this, &Ekos::Manager::deviceDisconnected); connect(devInterface, &ISD::GDInterface::propertyDefined, this, &Ekos::Manager::processNewProperty); connect(devInterface, &ISD::GDInterface::interfaceDefined, this, &Ekos::Manager::syncActiveDevices); connect(devInterface, &ISD::GDInterface::numberUpdated, this, &Ekos::Manager::processNewNumber); connect(devInterface, &ISD::GDInterface::switchUpdated, this, &Ekos::Manager::processNewSwitch); connect(devInterface, &ISD::GDInterface::textUpdated, this, &Ekos::Manager::processNewText); connect(devInterface, &ISD::GDInterface::lightUpdated, this, &Ekos::Manager::processNewLight); connect(devInterface, &ISD::GDInterface::BLOBUpdated, this, &Ekos::Manager::processNewBLOB); if (currentProfile->isStellarMate) { connect(devInterface, &ISD::GDInterface::systemPortDetected, [this, devInterface]() { if (!serialPortAssistant) { serialPortAssistant.reset(new SerialPortAssistant(currentProfile, this)); serialPortAssistantB->setEnabled(true); } uint32_t driverInterface = devInterface->getDriverInterface(); // Ignore CCD interface if (driverInterface & INDI::BaseDevice::CCD_INTERFACE) return; if (driverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE || driverInterface & INDI::BaseDevice::FOCUSER_INTERFACE || driverInterface & INDI::BaseDevice::FILTER_INTERFACE || driverInterface & INDI::BaseDevice::AUX_INTERFACE || driverInterface & INDI::BaseDevice::GPS_INTERFACE) serialPortAssistant->addDevice(devInterface); if (Options::autoLoadSerialAssistant()) serialPortAssistant->show(); }); } if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); //controlPanelB->setEnabled(true); if (m_LocalMode == false && nDevices == 0) { if (currentProfile->autoConnect) appendLogText(i18n("Remote devices established.")); else appendLogText(i18n("Remote devices established. Please connect devices.")); } } } void Manager::deviceConnected() { connectB->setEnabled(false); disconnectB->setEnabled(true); processINDIB->setEnabled(false); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (Options::verboseLogging()) { ISD::GDInterface * device = qobject_cast(sender()); qCInfo(KSTARS_EKOS) << device->getDeviceName() << "Version:" << device->getDriverVersion() << "Interface:" << device->getDriverInterface() << "is connected."; } int nConnectedDevices = 0; foreach (ISD::GDInterface * device, genericDevices) { if (device->isConnected()) nConnectedDevices++; } qCDebug(KSTARS_EKOS) << nConnectedDevices << " devices connected out of " << genericDevices.count(); if (nConnectedDevices >= currentProfile->drivers.count()) //if (nConnectedDevices >= genericDevices.count()) { m_indiStatus = Ekos::Success; qCInfo(KSTARS_EKOS) << "All INDI devices are now connected."; } else m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); ISD::GDInterface * dev = static_cast(sender()); if (dev->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE) { if (mountProcess.get() != nullptr) { mountProcess->setEnabled(true); if (alignProcess.get() != nullptr) alignProcess->setEnabled(true); } } else if (dev->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(true); if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); if (alignProcess.get() != nullptr) { if (mountProcess.get() && mountProcess->isEnabled()) alignProcess->setEnabled(true); else alignProcess->setEnabled(false); } if (guideProcess.get() != nullptr) guideProcess->setEnabled(true); } else if (dev->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); } if (Options::neverLoadConfig()) return; INDIConfig tConfig = Options::loadConfigOnConnection() ? LOAD_LAST_CONFIG : LOAD_DEFAULT_CONFIG; foreach (ISD::GDInterface * device, genericDevices) { if (device == dev) { connect(dev, &ISD::GDInterface::switchUpdated, this, &Ekos::Manager::watchDebugProperty); ISwitchVectorProperty * configProp = device->getBaseDevice()->getSwitch("CONFIG_PROCESS"); if (configProp && configProp->s == IPS_IDLE) device->setConfig(tConfig); break; } } } void Manager::deviceDisconnected() { ISD::GDInterface * dev = static_cast(sender()); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (dev != nullptr) { if (dev->getState("CONNECTION") == IPS_ALERT) m_indiStatus = Ekos::Error; else if (dev->getState("CONNECTION") == IPS_BUSY) m_indiStatus = Ekos::Pending; else m_indiStatus = Ekos::Idle; if (Options::verboseLogging()) qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected."; appendLogText(i18n("%1 is disconnected.", dev->getDeviceName())); } else m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); processINDIB->setEnabled(true); if (dev != nullptr && dev->getBaseDevice() && (dev->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) { if (mountProcess.get() != nullptr) mountProcess->setEnabled(false); } // Do not disable modules on device connection loss, let them handle it /* else if (dev->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(false); if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); if (alignProcess.get() != nullptr) alignProcess->setEnabled(false); if (guideProcess.get() != nullptr) guideProcess->setEnabled(false); } else if (dev->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); }*/ } void Manager::setTelescope(ISD::GDInterface * scopeDevice) { //mount = scopeDevice; managedDevices[KSTARS_TELESCOPE] = scopeDevice; appendLogText(i18n("%1 is online.", scopeDevice->getDeviceName())); // connect(scopeDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, // SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); initMount(); mountProcess->setTelescope(scopeDevice); double primaryScopeFL = 0, primaryScopeAperture = 0, guideScopeFL = 0, guideScopeAperture = 0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver mountProcess->setTelescopeInfo(QList() << primaryScopeFL << primaryScopeAperture << guideScopeFL << guideScopeAperture); if (guideProcess.get() != nullptr) { guideProcess->setTelescope(scopeDevice); guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(scopeDevice); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } // if (domeProcess.get() != nullptr) // domeProcess->setTelescope(scopeDevice); ekosLiveClient->message()->sendMounts(); ekosLiveClient->message()->sendScopes(); } void Manager::setCCD(ISD::GDInterface * ccdDevice) { // No duplicates for (auto oneCCD : findDevices(KSTARS_CCD)) if (oneCCD == ccdDevice) return; managedDevices.insertMulti(KSTARS_CCD, ccdDevice); initCapture(); captureProcess->setEnabled(true); captureProcess->addCCD(ccdDevice); QString primaryCCD, guiderCCD; // Only look for primary & guider CCDs if we can tell a difference between them // otherwise rely on saved options if (currentProfile->ccd() != currentProfile->guider()) { foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (QString(device->getDeviceName()).startsWith(currentProfile->ccd(), Qt::CaseInsensitive)) primaryCCD = QString(device->getDeviceName()); else if (QString(device->getDeviceName()).startsWith(currentProfile->guider(), Qt::CaseInsensitive)) guiderCCD = QString(device->getDeviceName()); } } bool rc = false; if (Options::defaultCaptureCCD().isEmpty() == false) rc = captureProcess->setCamera(Options::defaultCaptureCCD()); if (rc == false && primaryCCD.isEmpty() == false) captureProcess->setCamera(primaryCCD); initFocus(); focusProcess->addCCD(ccdDevice); rc = false; if (Options::defaultFocusCCD().isEmpty() == false) rc = focusProcess->setCamera(Options::defaultFocusCCD()); if (rc == false && primaryCCD.isEmpty() == false) focusProcess->setCamera(primaryCCD); initAlign(); alignProcess->addCCD(ccdDevice); rc = false; if (Options::defaultAlignCCD().isEmpty() == false) rc = alignProcess->setCamera(Options::defaultAlignCCD()); if (rc == false && primaryCCD.isEmpty() == false) alignProcess->setCamera(primaryCCD); initGuide(); guideProcess->addCamera(ccdDevice); rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false && guiderCCD.isEmpty() == false) guideProcess->setCamera(guiderCCD); appendLogText(i18n("%1 is online.", ccdDevice->getDeviceName())); // connect(ccdDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, // SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); if (managedDevices.contains(KSTARS_TELESCOPE)) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); } } void Manager::setFilter(ISD::GDInterface * filterDevice) { // No duplicates if (findDevices(KSTARS_FILTER).contains(filterDevice) == false) managedDevices.insertMulti(KSTARS_FILTER, filterDevice); appendLogText(i18n("%1 filter is online.", filterDevice->getDeviceName())); initCapture(); // connect(filterDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, // SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); // connect(filterDevice, SIGNAL(textUpdated(ITextVectorProperty *)), this, // SLOT(processNewText(ITextVectorProperty *)), Qt::UniqueConnection); captureProcess->addFilter(filterDevice); initFocus(); focusProcess->addFilter(filterDevice); initAlign(); alignProcess->addFilter(filterDevice); } void Manager::setFocuser(ISD::GDInterface * focuserDevice) { // No duplicates if (findDevices(KSTARS_FOCUSER).contains(focuserDevice) == false) managedDevices.insertMulti(KSTARS_FOCUSER, focuserDevice); initCapture(); initFocus(); focusProcess->addFocuser(focuserDevice); if (Options::defaultFocusFocuser().isEmpty() == false) focusProcess->setFocuser(Options::defaultFocusFocuser()); appendLogText(i18n("%1 focuser is online.", focuserDevice->getDeviceName())); } void Manager::setDome(ISD::GDInterface * domeDevice) { managedDevices[KSTARS_DOME] = domeDevice; initDome(); domeProcess->setDome(domeDevice); if (captureProcess.get() != nullptr) captureProcess->setDome(domeDevice); if (alignProcess.get() != nullptr) alignProcess->setDome(domeDevice); // if (managedDevices.contains(KSTARS_TELESCOPE)) // domeProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); appendLogText(i18n("%1 is online.", domeDevice->getDeviceName())); } void Manager::setWeather(ISD::GDInterface * weatherDevice) { managedDevices[KSTARS_WEATHER] = weatherDevice; initWeather(); weatherProcess->setWeather(weatherDevice); appendLogText(i18n("%1 is online.", weatherDevice->getDeviceName())); } void Manager::setDustCap(ISD::GDInterface * dustCapDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(dustCapDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, dustCapDevice); initDustCap(); dustCapProcess->setDustCap(dustCapDevice); appendLogText(i18n("%1 is online.", dustCapDevice->getDeviceName())); if (captureProcess.get() != nullptr) captureProcess->setDustCap(dustCapDevice); DarkLibrary::Instance()->setRemoteCap(dustCapDevice); } void Manager::setLightBox(ISD::GDInterface * lightBoxDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(lightBoxDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, lightBoxDevice); if (captureProcess.get() != nullptr) captureProcess->setLightBox(lightBoxDevice); } void Manager::removeDevice(ISD::GDInterface * devInterface) { // switch (devInterface->getType()) // { // case KSTARS_CCD: // removeTabs(); // break; // default: // break; // } if (alignProcess) alignProcess->removeDevice(devInterface); if (captureProcess) captureProcess->removeDevice(devInterface); if (focusProcess) focusProcess->removeDevice(devInterface); if (mountProcess) mountProcess->removeDevice(devInterface); if (guideProcess) guideProcess->removeDevice(devInterface); if (domeProcess) domeProcess->removeDevice(devInterface); if (weatherProcess) weatherProcess->removeDevice(devInterface); if (dustCapProcess) { dustCapProcess->removeDevice(devInterface); DarkLibrary::Instance()->removeDevice(devInterface); } appendLogText(i18n("%1 is offline.", devInterface->getDeviceName())); // #1 Remove from Generic Devices // Generic devices are ALL the devices we receive from INDI server // Whether Ekos cares about them (i.e. selected equipment) or extra devices we // do not care about for (auto &device : genericDevices) { if (device->getDeviceName() == devInterface->getDeviceName()) { genericDevices.removeOne(device); } } // #2 Remove from Ekos Managed Device // Managed devices are devices selected by the user in the device profile for (auto it = managedDevices.begin(); it != managedDevices.end();) { if (it.value()->getDeviceName() == devInterface->getDeviceName()) { it = managedDevices.erase(it); } else ++it; } if (managedDevices.isEmpty()) { cleanDevices(); removeTabs(); } } void Manager::processNewText(ITextVectorProperty * tvp) { ekosLiveClient.get()->message()->processNewText(tvp); if (!strcmp(tvp->name, "FILTER_NAME")) { ekosLiveClient.get()->message()->sendFilterWheels(); } if (!strcmp(tvp->name, "ACTIVE_DEVICES")) { syncActiveDevices(); } } void Manager::processNewSwitch(ISwitchVectorProperty * svp) { ekosLiveClient.get()->message()->processNewSwitch(svp); } void Manager::processNewLight(ILightVectorProperty * lvp) { ekosLiveClient.get()->message()->processNewLight(lvp); } void Manager::processNewBLOB(IBLOB * bp) { ekosLiveClient.get()->media()->processNewBLOB(bp); } void Manager::processNewNumber(INumberVectorProperty * nvp) { ekosLiveClient.get()->message()->processNewNumber(nvp); if (!strcmp(nvp->name, "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(nvp->name, "CCD_INFO") || !strcmp(nvp->name, "GUIDER_INFO") || !strcmp(nvp->name, "CCD_FRAME") || !strcmp(nvp->name, "GUIDER_FRAME")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } /* if (!strcmp(nvp->name, "FILTER_SLOT")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); } */ } void Manager::processNewProperty(INDI::Property * prop) { ISD::GenericDevice * deviceInterface = qobject_cast(sender()); if (!strcmp(prop->getName(), "CONNECTION") && currentProfile->autoConnect) { // Check if we need to do any mappings const QString port = m_ProfileMapping.value(QString(deviceInterface->getDeviceName())).toString(); // If we don't have port mapping, then we connect immediately. if (port.isEmpty()) deviceInterface->Connect(); return; } if (!strcmp(prop->getName(), "DEVICE_PORT")) { // Check if we need to do any mappings const QString port = m_ProfileMapping.value(QString(deviceInterface->getDeviceName())).toString(); if (!port.isEmpty()) { ITextVectorProperty *tvp = prop->getText(); IUSaveText(&(tvp->tp[0]), port.toLatin1().data()); deviceInterface->getDriverInfo()->getClientManager()->sendNewText(tvp); // Now connect if we need to. if (currentProfile->autoConnect) deviceInterface->Connect(); return; } } // Check if we need to turn on DEBUG for logging purposes if (!strcmp(prop->getName(), "DEBUG")) { uint16_t interface = deviceInterface->getDriverInterface(); if ( opsLogs->getINDIDebugInterface() & interface ) { // Check if we need to enable debug logging for the INDI drivers. ISwitchVectorProperty * debugSP = prop->getSwitch(); debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); } } // Handle debug levels for logging purposes if (!strcmp(prop->getName(), "DEBUG_LEVEL")) { uint16_t interface = deviceInterface->getDriverInterface(); // Check if the logging option for the specific device class is on and if the device interface matches it. if ( opsLogs->getINDIDebugInterface() & interface ) { // Turn on everything ISwitchVectorProperty * debugLevel = prop->getSwitch(); for (int i = 0; i < debugLevel->nsp; i++) debugLevel->sp[i].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugLevel); } } if (!strcmp(prop->getName(), "ACTIVE_DEVICES")) { if (deviceInterface->getDriverInterface() > 0) syncActiveDevices(); } if (!strcmp(prop->getName(), "TELESCOPE_INFO") || !strcmp(prop->getName(), "TELESCOPE_SLEW_RATE") || !strcmp(prop->getName(), "TELESCOPE_PARK")) { ekosLiveClient.get()->message()->sendMounts(); ekosLiveClient.get()->message()->sendScopes(); } if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "CCD_TEMPERATURE") || !strcmp(prop->getName(), "CCD_ISO") || !strcmp(prop->getName(), "CCD_GAIN") || !strcmp(prop->getName(), "CCD_CONTROLS")) { ekosLiveClient.get()->message()->sendCameras(); ekosLiveClient.get()->media()->registerCameras(); } if (!strcmp(prop->getName(), "ABS_DOME_POSITION") || !strcmp(prop->getName(), "DOME_ABORT_MOTION") || !strcmp(prop->getName(), "DOME_PARK")) { ekosLiveClient.get()->message()->sendDomes(); } if (!strcmp(prop->getName(), "CAP_PARK") || !strcmp(prop->getName(), "FLAT_LIGHT_CONTROL")) { ekosLiveClient.get()->message()->sendCaps(); } if (!strcmp(prop->getName(), "FILTER_NAME")) ekosLiveClient.get()->message()->sendFilterWheels(); if (!strcmp(prop->getName(), "FILTER_NAME")) filterManager.data()->initFilterProperties(); if (!strcmp(prop->getName(), "CONFIRM_FILTER_SET")) filterManager.data()->initFilterProperties(); if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "GUIDER_INFO")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } if (!strcmp(prop->getName(), "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(prop->getName(), "GUIDER_EXPOSURE")) { foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (device->getDeviceName() == prop->getDeviceName()) { initCapture(); initGuide(); useGuideHead = true; captureProcess->addGuideHead(device); guideProcess->addGuideHead(device); bool rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false) guideProcess->setCamera(QString(device->getDeviceName()) + QString(" Guider")); return; } } return; } if (!strcmp(prop->getName(), "CCD_FRAME_TYPE")) { if (captureProcess.get() != nullptr) { foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (device->getDeviceName() == prop->getDeviceName()) { captureProcess->syncFrameType(device); return; } } } return; } if (!strcmp(prop->getName(), "CCD_ISO")) { if (captureProcess.get() != nullptr) captureProcess->checkCCD(); return; } if (!strcmp(prop->getName(), "TELESCOPE_PARK") && managedDevices.contains(KSTARS_TELESCOPE)) { if (captureProcess.get() != nullptr) captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); if (mountProcess.get() != nullptr) mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); return; } /* if (!strcmp(prop->getName(), "FILTER_NAME")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); return; } */ if (!strcmp(prop->getName(), "ASTROMETRY_SOLVER")) { foreach (ISD::GDInterface * device, genericDevices) { if (device->getDeviceName() == prop->getDeviceName()) { initAlign(); alignProcess->setAstrometryDevice(device); break; } } } if (!strcmp(prop->getName(), "ABS_ROTATOR_ANGLE")) { managedDevices[KSTARS_ROTATOR] = deviceInterface; if (captureProcess.get() != nullptr) captureProcess->setRotator(deviceInterface); if (alignProcess.get() != nullptr) alignProcess->setRotator(deviceInterface); } if (!strcmp(prop->getName(), "GPS_REFRESH")) { managedDevices.insertMulti(KSTARS_AUXILIARY, deviceInterface); if (mountProcess.get() != nullptr) mountProcess->setGPS(deviceInterface); } if (focusProcess.get() != nullptr && strstr(prop->getName(), "FOCUS_")) { focusProcess->checkFocuser(); } } const QList &Manager::getAllDevices() const { return genericDevices; } QList Manager::findDevices(DeviceFamily type) { QList deviceList; QMapIterator i(managedDevices); while (i.hasNext()) { i.next(); if (i.key() == type) deviceList.append(i.value()); } return deviceList; } QList Manager::findDevicesByInterface(uint32_t interface) { QList deviceList; for (const auto dev : genericDevices) { uint32_t devInterface = dev->getDriverInterface(); if (devInterface & interface) deviceList.append(dev); } return deviceList; } void Manager::processTabChange() { QWidget * currentWidget = toolsWidget->currentWidget(); //if (focusProcess.get() != nullptr && currentWidget != focusProcess) //focusProcess->resetFrame(); if (alignProcess.get() && alignProcess.get() == currentWidget) { if (alignProcess->isEnabled() == false && captureProcess->isEnabled()) { if (managedDevices[KSTARS_CCD]->isConnected() && managedDevices.contains(KSTARS_TELESCOPE)) { if (alignProcess->isParserOK()) alignProcess->setEnabled(true); //#ifdef Q_OS_WIN else if (Options::solverBackend() == Ekos::Align::SOLVER_ASTROMETRYNET) { // If current setting is remote astrometry and profile doesn't contain // remote astrometry, then we switch to online solver. Otherwise, the whole align // module remains disabled and the user cannot change re-enable it since he cannot select online solver ProfileInfo * pi = getCurrentProfile(); if (Options::astrometrySolverType() == Ekos::Align::SOLVER_REMOTE && pi->aux1() != "Astrometry" && pi->aux2() != "Astrometry" && pi->aux3() != "Astrometry" && pi->aux4() != "Astrometry") { Options::setAstrometrySolverType(Ekos::Align::SOLVER_ONLINE); alignModule()->setAstrometrySolverType(Ekos::Align::SOLVER_ONLINE); alignProcess->setEnabled(true); } } //#endif } } alignProcess->checkCCD(); } else if (captureProcess.get() != nullptr && currentWidget == captureProcess.get()) { captureProcess->checkCCD(); } else if (focusProcess.get() != nullptr && currentWidget == focusProcess.get()) { focusProcess->checkCCD(); } else if (guideProcess.get() != nullptr && currentWidget == guideProcess.get()) { guideProcess->checkCCD(); } updateLog(); } void Manager::updateLog() { QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) ekosLogOut->setPlainText(m_LogText.join("\n")); else if (currentWidget == alignProcess.get()) ekosLogOut->setPlainText(alignProcess->getLogText()); else if (currentWidget == captureProcess.get()) ekosLogOut->setPlainText(captureProcess->getLogText()); else if (currentWidget == focusProcess.get()) ekosLogOut->setPlainText(focusProcess->getLogText()); else if (currentWidget == guideProcess.get()) ekosLogOut->setPlainText(guideProcess->getLogText()); else if (currentWidget == mountProcess.get()) ekosLogOut->setPlainText(mountProcess->getLogText()); else if (currentWidget == schedulerProcess.get()) ekosLogOut->setPlainText(schedulerProcess->getLogText()); else if (currentWidget == observatoryProcess.get()) ekosLogOut->setPlainText(observatoryProcess->getLogText()); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif } void Manager::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS) << text; emit newLog(text); updateLog(); } void Manager::clearLog() { QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) { m_LogText.clear(); updateLog(); } else if (currentWidget == alignProcess.get()) alignProcess->clearLog(); else if (currentWidget == captureProcess.get()) captureProcess->clearLog(); else if (currentWidget == focusProcess.get()) focusProcess->clearLog(); else if (currentWidget == guideProcess.get()) guideProcess->clearLog(); else if (currentWidget == mountProcess.get()) mountProcess->clearLog(); else if (currentWidget == schedulerProcess.get()) schedulerProcess->clearLog(); else if (currentWidget == observatoryProcess.get()) observatoryProcess->clearLog(); } void Manager::initCapture() { if (captureProcess.get() != nullptr) return; captureProcess.reset(new Ekos::Capture()); captureProcess->setEnabled(false); int index = toolsWidget->addTab(captureProcess.get(), QIcon(":/icons/ekos_ccd.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18nc("Charge-Coupled Device", "CCD")); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } connect(captureProcess.get(), &Ekos::Capture::newLog, this, &Ekos::Manager::updateLog); connect(captureProcess.get(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus); connect(captureProcess.get(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress); connect(captureProcess.get(), &Ekos::Capture::driverTimedout, this, &Ekos::Manager::restartDriver); connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString & filename, const QString & previewFITS) { if (Options::useSummaryPreview() && QFile::exists(filename)) { if (Options::autoImageToFITS()) { if (previewFITS.isEmpty() == false) summaryPreview->loadFITS(previewFITS); } else summaryPreview->loadFITS(filename); } }); connect(captureProcess.get(), &Ekos::Capture::newDownloadProgress, this, &Ekos::Manager::updateDownloadProgress); connect(captureProcess.get(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress); captureGroup->setEnabled(true); sequenceProgress->setEnabled(true); captureProgress->setEnabled(true); imageProgress->setEnabled(true); captureProcess->setFilterManager(filterManager); if (!capturePI) { capturePI = new QProgressIndicator(captureProcess.get()); captureStatusLayout->insertWidget(0, capturePI); } foreach (ISD::GDInterface * device, findDevices(KSTARS_AUXILIARY)) { if (device->getDriverInterface() & INDI::BaseDevice::DUSTCAP_INTERFACE) captureProcess->setDustCap(device); if (device->getDriverInterface() & INDI::BaseDevice::LIGHTBOX_INTERFACE) captureProcess->setLightBox(device); } if (managedDevices.contains(KSTARS_DOME)) { captureProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { captureProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Capture"); } void Manager::initAlign() { if (alignProcess.get() != nullptr) return; alignProcess.reset(new Ekos::Align(currentProfile)); double primaryScopeFL = 0, primaryScopeAperture = 0, guideScopeFL = 0, guideScopeAperture = 0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setEnabled(false); int index = toolsWidget->addTab(alignProcess.get(), QIcon(":/icons/ekos_align.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Align")); connect(alignProcess.get(), &Ekos::Align::newLog, this, &Ekos::Manager::updateLog); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } alignProcess->setFilterManager(filterManager); if (managedDevices.contains(KSTARS_DOME)) { alignProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { alignProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Align"); } void Manager::initFocus() { if (focusProcess.get() != nullptr) return; focusProcess.reset(new Ekos::Focus()); int index = toolsWidget->addTab(focusProcess.get(), QIcon(":/icons/ekos_focus.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Focus")); // Focus <---> Manager connections connect(focusProcess.get(), &Ekos::Focus::newLog, this, &Ekos::Manager::updateLog); connect(focusProcess.get(), &Ekos::Focus::newStatus, this, &Ekos::Manager::setFocusStatus); connect(focusProcess.get(), &Ekos::Focus::newStarPixmap, this, &Ekos::Manager::updateFocusStarPixmap); connect(focusProcess.get(), &Ekos::Focus::newProfilePixmap, this, &Ekos::Manager::updateFocusProfilePixmap); connect(focusProcess.get(), &Ekos::Focus::newHFR, this, &Ekos::Manager::updateCurrentHFR); // Focus <---> Filter Manager connections focusProcess->setFilterManager(filterManager); connect(filterManager.data(), &Ekos::FilterManager::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::newStatus, filterManager.data(), &Ekos::FilterManager::setFocusStatus, Qt::UniqueConnection); connect(filterManager.data(), &Ekos::FilterManager::newFocusOffset, focusProcess.get(), &Ekos::Focus::adjustFocusOffset, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::focusPositionAdjusted, filterManager.data(), &Ekos::FilterManager::setFocusOffsetComplete, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::absolutePositionChanged, filterManager.data(), &Ekos::FilterManager::setFocusAbsolutePosition, Qt::UniqueConnection); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } focusGroup->setEnabled(true); if (!focusPI) { focusPI = new QProgressIndicator(focusProcess.get()); focusStatusLayout->insertWidget(0, focusPI); } connectModules(); emit newModule("Focus"); } void Manager::updateCurrentHFR(double newHFR, int position) { currentHFR->setText(QString("%1").arg(newHFR, 0, 'f', 2) + " px"); QJsonObject cStatus = { {"hfr", newHFR}, {"pos", position} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void Manager::updateSigmas(double ra, double de) { errRA->setText(QString::number(ra, 'f', 2) + "\""); errDEC->setText(QString::number(de, 'f', 2) + "\""); QJsonObject cStatus = { {"rarms", ra}, {"derms", de} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void Manager::initMount() { if (mountProcess.get() != nullptr) return; mountProcess.reset(new Ekos::Mount()); int index = toolsWidget->addTab(mountProcess.get(), QIcon(":/icons/ekos_mount.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Mount")); connect(mountProcess.get(), &Ekos::Mount::newLog, this, &Ekos::Manager::updateLog); connect(mountProcess.get(), &Ekos::Mount::newCoords, this, &Ekos::Manager::updateMountCoords); connect(mountProcess.get(), &Ekos::Mount::newStatus, this, &Ekos::Manager::updateMountStatus); connect(mountProcess.get(), &Ekos::Mount::newTarget, [&](const QString & target) { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); connect(mountProcess.get(), &Ekos::Mount::slewRateChanged, [&](int slewRate) { QJsonObject status = { { "slewRate", slewRate} }; ekosLiveClient.get()->message()->updateMountStatus(status); } ); foreach (ISD::GDInterface * device, findDevices(KSTARS_AUXILIARY)) { if (device->getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE) mountProcess->setGPS(device); } if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } if (!mountPI) { mountPI = new QProgressIndicator(mountProcess.get()); mountStatusLayout->insertWidget(0, mountPI); } mountGroup->setEnabled(true); connectModules(); emit newModule("Mount"); } void Manager::initGuide() { if (guideProcess.get() == nullptr) { guideProcess.reset(new Ekos::Guide()); double primaryScopeFL = 0, primaryScopeAperture = 0, guideScopeFL = 0, guideScopeAperture = 0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } //if ( (haveGuider || ccdCount > 1 || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess) == -1) if ((findDevices(KSTARS_CCD).isEmpty() == false || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess.get()) == -1) { //if (mount && mount->isConnected()) if (managedDevices.contains(KSTARS_TELESCOPE) && managedDevices[KSTARS_TELESCOPE]->isConnected()) guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); int index = toolsWidget->addTab(guideProcess.get(), QIcon(":/icons/ekos_guide.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Guide")); connect(guideProcess.get(), &Ekos::Guide::newLog, this, &Ekos::Manager::updateLog); connect(guideProcess.get(), &Ekos::Guide::driverTimedout, this, &Ekos::Manager::restartDriver); guideGroup->setEnabled(true); if (!guidePI) { guidePI = new QProgressIndicator(guideProcess.get()); guideStatusLayout->insertWidget(0, guidePI); } connect(guideProcess.get(), &Ekos::Guide::newStatus, this, &Ekos::Manager::updateGuideStatus); connect(guideProcess.get(), &Ekos::Guide::newStarPixmap, this, &Ekos::Manager::updateGuideStarPixmap); connect(guideProcess.get(), &Ekos::Guide::newProfilePixmap, this, &Ekos::Manager::updateGuideProfilePixmap); connect(guideProcess.get(), &Ekos::Guide::newAxisSigma, this, &Ekos::Manager::updateSigmas); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } } connectModules(); emit newModule("Guide"); } void Manager::initDome() { if (domeProcess.get() != nullptr) return; domeProcess.reset(new Ekos::Dome()); connect(domeProcess.get(), &Ekos::Dome::newStatus, [&](ISD::Dome::Status newStatus) { // For roll-off domes // cw ---> unparking // ccw --> parking if (domeProcess->isRolloffRoof() && (newStatus == ISD::Dome::DOME_MOVING_CW || newStatus == ISD::Dome::DOME_MOVING_CCW)) { newStatus = (newStatus == ISD::Dome::DOME_MOVING_CW) ? ISD::Dome::DOME_UNPARKING : ISD::Dome::DOME_PARKING; } QJsonObject status = { { "status", ISD::Dome::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); connect(domeProcess.get(), &Ekos::Dome::azimuthPositionChanged, [&](double pos) { QJsonObject status = { { "az", pos} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); initObservatory(nullptr, domeProcess.get()); emit newModule("Dome"); ekosLiveClient->message()->sendDomes(); } void Manager::initWeather() { if (weatherProcess.get() != nullptr) return; weatherProcess.reset(new Ekos::Weather()); initObservatory(weatherProcess.get(), nullptr); emit newModule("Weather"); } void Manager::initObservatory(Weather *weather, Dome *dome) { if (observatoryProcess.get() == nullptr) { // Initialize the Observatory Module observatoryProcess.reset(new Ekos::Observatory()); int index = toolsWidget->addTab(observatoryProcess.get(), QIcon(":/icons/ekos_observatory.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Observatory")); connect(observatoryProcess.get(), &Ekos::Observatory::newLog, this, &Ekos::Manager::updateLog); } Observatory *obs = observatoryProcess.get(); if (weather != nullptr) obs->getWeatherModel()->initModel(weather); if (dome != nullptr) obs->getDomeModel()->initModel(dome); emit newModule("Observatory"); } void Manager::initDustCap() { if (dustCapProcess.get() != nullptr) return; dustCapProcess.reset(new Ekos::DustCap()); connect(dustCapProcess.get(), &Ekos::DustCap::newStatus, [&](ISD::DustCap::Status newStatus) { QJsonObject status = { { "status", ISD::DustCap::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); connect(dustCapProcess.get(), &Ekos::DustCap::lightToggled, [&](bool enabled) { QJsonObject status = { { "lightS", enabled} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); connect(dustCapProcess.get(), &Ekos::DustCap::lightIntensityChanged, [&](uint16_t value) { QJsonObject status = { { "lightB", value} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); emit newModule("DustCap"); ekosLiveClient->message()->sendCaps(); } void Manager::setST4(ISD::ST4 * st4Driver) { appendLogText(i18n("Guider port from %1 is ready.", st4Driver->getDeviceName())); useST4 = true; initGuide(); guideProcess->addST4(st4Driver); if (Options::defaultST4Driver().isEmpty() == false) guideProcess->setST4(Options::defaultST4Driver()); } void Manager::removeTabs() { disconnect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange); for (int i = 2; i < toolsWidget->count(); i++) toolsWidget->removeTab(i); alignProcess.reset(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); mountProcess.reset(); domeProcess.reset(); weatherProcess.reset(); observatoryProcess.reset(); dustCapProcess.reset(); managedDevices.clear(); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); } bool Manager::isRunning(const QString &process) { QProcess ps; #ifdef Q_OS_OSX ps.start("pgrep", QStringList() << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.length() > 0; #else ps.start("ps", QStringList() << "-o" << "comm" << "--no-headers" << "-C" << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.contains(process); #endif } void Manager::addObjectToScheduler(SkyObject * object) { if (schedulerProcess.get() != nullptr) schedulerProcess->addObject(object); } QString Manager::getCurrentJobName() { return schedulerProcess->getCurrentJobName(); } bool Manager::setProfile(const QString &profileName) { int index = profileCombo->findText(profileName); if (index < 0) return false; profileCombo->setCurrentIndex(index); return true; } void Manager::editNamedProfile(const QJsonObject &profileInfo) { ProfileEditor editor(this); setProfile(profileInfo["name"].toString()); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); editor.setSettings(profileInfo); editor.saveProfile(); } void Manager::addNamedProfile(const QJsonObject &profileInfo) { ProfileEditor editor(this); editor.setSettings(profileInfo); editor.saveProfile(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); currentProfile = getCurrentProfile(); } void Manager::deleteNamedProfile(const QString &name) { currentProfile = getCurrentProfile(); for (auto &pi : profiles) { // Do not delete an actively running profile // Do not delete simulator profile if (pi->name == "Simulators" || pi->name != name || (pi.get() == currentProfile && ekosStatus() != Idle)) continue; KStarsData::Instance()->userdb()->DeleteProfile(pi.get()); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); return; } } QJsonObject Manager::getNamedProfile(const QString &name) { QJsonObject profileInfo; // Get current profile for (auto &pi : profiles) { if (name == pi->name) return pi->toJson(); } return QJsonObject(); } QStringList Manager::getProfiles() { QStringList profiles; for (int i = 0; i < profileCombo->count(); i++) profiles << profileCombo->itemText(i); return profiles; } void Manager::addProfile() { ProfileEditor editor(this); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } void Manager::editProfile() { ProfileEditor editor(this); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); if (editor.exec() == QDialog::Accepted) { int currentIndex = profileCombo->currentIndex(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(currentIndex); } currentProfile = getCurrentProfile(); } void Manager::deleteProfile() { currentProfile = getCurrentProfile(); if (currentProfile->name == "Simulators") return; auto executeDeleteProfile = [&]() { KStarsData::Instance()->userdb()->DeleteProfile(currentProfile); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); }; connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeDeleteProfile]() { //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); KSMessageBox::Instance()->disconnect(this); executeDeleteProfile(); }); KSMessageBox::Instance()->questionYesNo(i18n("Are you sure you want to delete the profile?"), i18n("Confirm Delete")); } void Manager::wizardProfile() { ProfileWizard wz; if (wz.exec() != QDialog::Accepted) return; ProfileEditor editor(this); editor.setProfileName(wz.profileName); editor.setAuxDrivers(wz.selectedAuxDrivers()); if (wz.useInternalServer == false) editor.setHostPort(wz.host, wz.port); editor.setWebManager(wz.useWebManager); editor.setGuiderType(wz.selectedExternalGuider()); // Disable connection options editor.setConnectionOptionsEnabled(false); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } ProfileInfo * Manager::getCurrentProfile() { ProfileInfo * currProfile = nullptr; // Get current profile for (auto &pi : profiles) { if (profileCombo->currentText() == pi->name) { currProfile = pi.get(); break; } } return currProfile; } void Manager::updateProfileLocation(ProfileInfo * pi) { if (pi->city.isEmpty() == false) { bool cityFound = KStars::Instance()->setGeoLocation(pi->city, pi->province, pi->country); if (cityFound) appendLogText(i18n("Site location updated to %1.", KStarsData::Instance()->geo()->fullName())); else appendLogText(i18n("Failed to update site location to %1. City not found.", KStarsData::Instance()->geo()->fullName())); } } void Manager::updateMountStatus(ISD::Telescope::Status status) { static ISD::Telescope::Status lastStatus = ISD::Telescope::MOUNT_IDLE; if (status == lastStatus) return; lastStatus = status; mountStatus->setText(dynamic_cast(managedDevices[KSTARS_TELESCOPE])->getStatusString(status)); mountStatus->setStyleSheet(QString()); switch (status) { case ISD::Telescope::MOUNT_PARKING: case ISD::Telescope::MOUNT_SLEWING: case ISD::Telescope::MOUNT_MOVING: mountPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (mountPI->isAnimated() == false) mountPI->startAnimation(); break; case ISD::Telescope::MOUNT_TRACKING: mountPI->setColor(Qt::darkGreen); if (mountPI->isAnimated() == false) mountPI->startAnimation(); break; case ISD::Telescope::MOUNT_PARKED: mountStatus->setStyleSheet("font-weight:bold;background-color:red;border:2px solid black;"); if (mountPI->isAnimated()) mountPI->stopAnimation(); break; default: if (mountPI->isAnimated()) mountPI->stopAnimation(); } QJsonObject cStatus = { {"status", mountStatus->text()} }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::updateMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt) { raOUT->setText(ra); decOUT->setText(dec); azOUT->setText(az); altOUT->setText(alt); QJsonObject cStatus = { {"ra", dms::fromString(ra, false).Degrees()}, {"de", dms::fromString(dec, true).Degrees()}, {"az", dms::fromString(az, true).Degrees()}, {"at", dms::fromString(alt, true).Degrees()}, }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::updateCaptureStatus(Ekos::CaptureState status) { captureStatus->setText(Ekos::getCaptureStatusString(status)); captureProgress->setValue(captureProcess->getProgressPercentage()); overallCountDown.setHMS(0, 0, 0); overallCountDown = overallCountDown.addSecs(captureProcess->getOverallRemainingTime()); sequenceCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(captureProcess->getActiveJobRemainingTime()); if (status != Ekos::CAPTURE_ABORTED && status != Ekos::CAPTURE_COMPLETE && status != Ekos::CAPTURE_IDLE) { if (status == Ekos::CAPTURE_CAPTURING) capturePI->setColor(Qt::darkGreen); else capturePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (capturePI->isAnimated() == false) { capturePI->startAnimation(); countdownTimer.start(); } } else { if (capturePI->isAnimated()) { capturePI->stopAnimation(); countdownTimer.stop(); if (focusStatus->text() == "Complete") { if (focusPI->isAnimated()) focusPI->stopAnimation(); } imageProgress->setValue(0); sequenceLabel->setText(i18n("Sequence")); imageRemainingTime->setText("--:--:--"); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); } } QJsonObject cStatus = { {"status", captureStatus->text()}, {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(cStatus); } void Manager::updateCaptureProgress(Ekos::SequenceJob * job) { // Image is set to nullptr only on initial capture start up int completed = job->getCompleted(); // if (job->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) // completed = job->getCompleted() + 1; // else // completed = job->isPreview() ? job->getCompleted() : job->getCompleted() + 1; if (job->isPreview() == false) { sequenceLabel->setText(QString("Job # %1/%2 %3 (%4/%5)") .arg(captureProcess->getActiveJobID() + 1) .arg(captureProcess->getJobCount()) .arg(job->getFullPrefix()) .arg(completed) .arg(job->getCount())); } else sequenceLabel->setText(i18n("Preview")); sequenceProgress->setRange(0, job->getCount()); sequenceProgress->setValue(completed); QJsonObject status = { {"seqv", completed}, {"seqr", job->getCount()}, {"seql", sequenceLabel->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); if (job->getStatus() == SequenceJob::JOB_BUSY) { QString uuid = QUuid::createUuid().toString(); uuid = uuid.remove(QRegularExpression("[-{}]")); // FITSView *image = job->getActiveChip()->getImageView(FITS_NORMAL); // ekosLiveClient.get()->media()->sendPreviewImage(image, uuid); // ekosLiveClient.get()->cloud()->sendPreviewImage(image, uuid); QString filename = job->property("filename").toString(); ekosLiveClient.get()->media()->sendPreviewImage(filename, uuid); if (job->isPreview() == false) ekosLiveClient.get()->cloud()->sendPreviewImage(filename, uuid); } } void Manager::updateDownloadProgress(double timeLeft) { imageCountDown.setHMS(0, 0, 0); imageCountDown = imageCountDown.addSecs(timeLeft); imageRemainingTime->setText(imageCountDown.toString("hh:mm:ss")); } void Manager::updateExposureProgress(Ekos::SequenceJob * job) { imageCountDown.setHMS(0, 0, 0); imageCountDown = imageCountDown.addSecs(job->getExposeLeft() + captureProcess->getEstimatedDownloadTime()); if (imageCountDown.hour() == 23) imageCountDown.setHMS(0, 0, 0); imageProgress->setRange(0, job->getExposure()); imageProgress->setValue(job->getExposeLeft()); imageRemainingTime->setText(imageCountDown.toString("hh:mm:ss")); QJsonObject status { {"expv", job->getExposeLeft()}, {"expr", job->getExposure()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void Manager::updateCaptureCountDown() { overallCountDown = overallCountDown.addSecs(-1); if (overallCountDown.hour() == 23) overallCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(-1); if (sequenceCountDown.hour() == 23) sequenceCountDown.setHMS(0, 0, 0); overallRemainingTime->setText(overallCountDown.toString("hh:mm:ss")); sequenceRemainingTime->setText(sequenceCountDown.toString("hh:mm:ss")); QJsonObject status = { {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void Manager::updateFocusStarPixmap(QPixmap &starPixmap) { if (starPixmap.isNull()) return; focusStarPixmap.reset(new QPixmap(starPixmap)); focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::updateFocusProfilePixmap(QPixmap &profilePixmap) { if (profilePixmap.isNull()) return; focusProfileImage->setPixmap(profilePixmap); } void Manager::setFocusStatus(Ekos::FocusState status) { focusStatus->setText(Ekos::getFocusStatusString(status)); if (status >= Ekos::FOCUS_PROGRESS) { focusPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else if (status == Ekos::FOCUS_COMPLETE && Options::enforceAutofocus() && captureProcess->getActiveJobID() != -1) { focusPI->setColor(Qt::darkGreen); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else { if (focusPI->isAnimated()) focusPI->stopAnimation(); } QJsonObject cStatus = { {"status", focusStatus->text()} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void Manager::updateGuideStatus(Ekos::GuideState status) { guideStatus->setText(Ekos::getGuideStatusString(status)); switch (status) { case Ekos::GUIDE_IDLE: case Ekos::GUIDE_CALIBRATION_ERROR: case Ekos::GUIDE_ABORTED: case Ekos::GUIDE_SUSPENDED: case Ekos::GUIDE_DITHERING_ERROR: case Ekos::GUIDE_CALIBRATION_SUCESS: if (guidePI->isAnimated()) guidePI->stopAnimation(); break; case Ekos::GUIDE_CALIBRATING: guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_GUIDING: guidePI->setColor(Qt::darkGreen); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_DITHERING: guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_DITHERING_SUCCESS: guidePI->setColor(Qt::darkGreen); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; default: if (guidePI->isAnimated()) guidePI->stopAnimation(); break; } QJsonObject cStatus = { {"status", guideStatus->text()} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void Manager::updateGuideStarPixmap(QPixmap &starPix) { if (starPix.isNull()) return; guideStarPixmap.reset(new QPixmap(starPix)); guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::updateGuideProfilePixmap(QPixmap &profilePix) { if (profilePix.isNull()) return; guideProfileImage->setPixmap(profilePix); } void Manager::setTarget(SkyObject * o) { mountTarget->setText(o->name()); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", o->name()}})); } void Manager::showEkosOptions() { QWidget * currentWidget = toolsWidget->currentWidget(); if (alignProcess.get() && alignProcess.get() == currentWidget) { KConfigDialog * alignSettings = KConfigDialog::exists("alignsettings"); if (alignSettings) { alignSettings->setEnabled(true); alignSettings->show(); } return; } if (guideProcess.get() && guideProcess.get() == currentWidget) { KConfigDialog::showDialog("guidesettings"); return; } if (ekosOptionsWidget == nullptr) { optionsB->click(); } else if (KConfigDialog::showDialog("settings")) { KConfigDialog * cDialog = KConfigDialog::exists("settings"); cDialog->setCurrentPage(ekosOptionsWidget); } } void Manager::getCurrentProfileTelescopeInfo(double &primaryFocalLength, double &primaryAperture, double &guideFocalLength, double &guideAperture) { ProfileInfo * pi = getCurrentProfile(); if (pi) { int primaryScopeID = 0, guideScopeID = 0; primaryScopeID = pi->primaryscope; guideScopeID = pi->guidescope; if (primaryScopeID > 0 || guideScopeID > 0) { // Get all OAL equipment filter list QList m_scopeList; KStarsData::Instance()->userdb()->GetAllScopes(m_scopeList); for(auto oneScope : m_scopeList) { if (oneScope->id().toInt() == primaryScopeID) { primaryFocalLength = oneScope->focalLength(); primaryAperture = oneScope->aperture(); } if (oneScope->id().toInt() == guideScopeID) { guideFocalLength = oneScope->focalLength(); guideAperture = oneScope->aperture(); } } qDeleteAll(m_scopeList); } } } void Manager::updateDebugInterfaces() { KSUtils::Logging::SyncFilterRules(); for (ISD::GDInterface * device : genericDevices) { INDI::Property * debugProp = device->getProperty("DEBUG"); ISwitchVectorProperty * debugSP = nullptr; if (debugProp) debugSP = debugProp->getSwitch(); else continue; // Check if the debug interface matches the driver device class if ( ( opsLogs->getINDIDebugInterface() & device->getDriverInterface() ) && debugSP->sp[0].s != ISS_ON) { debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Enabling debug logging for %1...", device->getDeviceName())); } else if ( !( opsLogs->getINDIDebugInterface() & device->getDriverInterface() ) && debugSP->sp[0].s != ISS_OFF) { debugSP->sp[0].s = ISS_OFF; debugSP->sp[1].s = ISS_ON; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Disabling debug logging for %1...", device->getDeviceName())); } if (opsLogs->isINDISettingsChanged()) device->setConfig(SAVE_CONFIG); } } void Manager::watchDebugProperty(ISwitchVectorProperty * svp) { if (!strcmp(svp->name, "DEBUG")) { ISD::GenericDevice * deviceInterface = qobject_cast(sender()); // We don't process pure general interfaces if (deviceInterface->getDriverInterface() == INDI::BaseDevice::GENERAL_INTERFACE) return; // If debug was turned off, but our logging policy requires it then turn it back on. // We turn on debug logging if AT LEAST one driver interface is selected by the logging settings if (svp->s == IPS_OK && svp->sp[0].s == ISS_OFF && (opsLogs->getINDIDebugInterface() & deviceInterface->getDriverInterface())) { svp->sp[0].s = ISS_ON; svp->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-enabling debug logging for %1...", deviceInterface->getDeviceName())); } // To turn off debug logging, NONE of the driver interfaces should be enabled in logging settings. // For example, if we have CCD+FilterWheel device and CCD + Filter Wheel logging was turned on in // the log settings, then if the user turns off only CCD logging, the debug logging is NOT // turned off until he turns off Filter Wheel logging as well. else if (svp->s == IPS_OK && svp->sp[0].s == ISS_ON && !(opsLogs->getINDIDebugInterface() & deviceInterface->getDriverInterface())) { svp->sp[0].s = ISS_OFF; svp->sp[1].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-disabling debug logging for %1...", deviceInterface->getDeviceName())); } } } void Manager::announceEvent(const QString &message, KSNotification::EventType event) { ekosLiveClient.get()->message()->sendEvent(message, event); } void Manager::connectModules() { // Guide <---> Capture connections if (captureProcess.get() && guideProcess.get()) { captureProcess.get()->disconnect(guideProcess.get()); guideProcess.get()->disconnect(captureProcess.get()); // Guide Limits connect(guideProcess.get(), &Ekos::Guide::newStatus, captureProcess.get(), &Ekos::Capture::setGuideStatus, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::newAxisDelta, captureProcess.get(), &Ekos::Capture::setGuideDeviation); // Dithering connect(captureProcess.get(), &Ekos::Capture::newStatus, guideProcess.get(), &Ekos::Guide::setCaptureStatus, Qt::UniqueConnection); // Guide Head connect(captureProcess.get(), &Ekos::Capture::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::guideChipUpdated, captureProcess.get(), &Ekos::Capture::setGuideChip, Qt::UniqueConnection); // Meridian Flip connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, guideProcess.get(), &Ekos::Guide::abort, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, guideProcess.get(), &Ekos::Guide::guideAfterMeridianFlip, Qt::UniqueConnection); } // Guide <---> Mount connections if (guideProcess.get() && mountProcess.get()) { // Parking connect(mountProcess.get(), &Ekos::Mount::newStatus, guideProcess.get(), &Ekos::Guide::setMountStatus, Qt::UniqueConnection); + connect(mountProcess.get(), &Ekos::Mount::newCoords, guideProcess.get(), &Ekos::Guide::setMountCoords, + Qt::UniqueConnection); + } // Focus <---> Guide connections if (guideProcess.get() && focusProcess.get()) { // Suspend connect(focusProcess.get(), &Ekos::Focus::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); } // Capture <---> Focus connections if (captureProcess.get() && focusProcess.get()) { // Check focus HFR value connect(captureProcess.get(), &Ekos::Capture::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); // Reset Focus connect(captureProcess.get(), &Ekos::Capture::resetFocus, focusProcess.get(), &Ekos::Focus::resetFrame, Qt::UniqueConnection); // New Focus Status connect(focusProcess.get(), &Ekos::Focus::newStatus, captureProcess.get(), &Ekos::Capture::setFocusStatus, Qt::UniqueConnection); // New Focus HFR connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); } // Capture <---> Align connections if (captureProcess.get() && alignProcess.get()) { // Alignment flag connect(alignProcess.get(), &Ekos::Align::newStatus, captureProcess.get(), &Ekos::Capture::setAlignStatus, Qt::UniqueConnection); // Solver data connect(alignProcess.get(), &Ekos::Align::newSolverResults, captureProcess.get(), &Ekos::Capture::setAlignResults, Qt::UniqueConnection); // Capture Status connect(captureProcess.get(), &Ekos::Capture::newStatus, alignProcess.get(), &Ekos::Align::setCaptureStatus, Qt::UniqueConnection); } // Capture <---> Mount connections if (captureProcess.get() && mountProcess.get()) { // Meridian Flip states connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, mountProcess.get(), &Ekos::Mount::disableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, mountProcess.get(), &Ekos::Mount::enableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::newMeridianFlipStatus, mountProcess.get(), &Ekos::Mount::meridianFlipStatusChanged, Qt::UniqueConnection); connect(mountProcess.get(), &Ekos::Mount::newMeridianFlipStatus, captureProcess.get(), &Ekos::Capture::meridianFlipStatusChanged, Qt::UniqueConnection); // Mount Status connect(mountProcess.get(), &Ekos::Mount::newStatus, captureProcess.get(), &Ekos::Capture::setMountStatus, Qt::UniqueConnection); } // Capture <---> EkosLive connections if (captureProcess.get() && ekosLiveClient.get()) { captureProcess.get()->disconnect(ekosLiveClient.get()->message()); connect(captureProcess.get(), &Ekos::Capture::dslrInfoRequested, ekosLiveClient.get()->message(), &EkosLive::Message::requestDSLRInfo); connect(captureProcess.get(), &Ekos::Capture::sequenceChanged, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSequence); connect(captureProcess.get(), &Ekos::Capture::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSettings); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Focus <---> Mount connections if (focusProcess.get() && mountProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, focusProcess.get(), &Ekos::Focus::setMountStatus, Qt::UniqueConnection); } // Mount <---> Align connections if (mountProcess.get() && alignProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, alignProcess.get(), &Ekos::Align::setMountStatus, Qt::UniqueConnection); } // Mount <---> Guide connections if (mountProcess.get() && guideProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::pierSideChanged, guideProcess.get(), &Ekos::Guide::setPierSide, Qt::UniqueConnection); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Align <--> EkosLive connections if (alignProcess.get() && ekosLiveClient.get()) { alignProcess.get()->disconnect(ekosLiveClient.get()->message()); alignProcess.get()->disconnect(ekosLiveClient.get()->media()); connect(alignProcess.get(), &Ekos::Align::newStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignStatus); connect(alignProcess.get(), &Ekos::Align::newSolution, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignSolution); connect(alignProcess.get(), &Ekos::Align::newPAHStage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHStage); connect(alignProcess.get(), &Ekos::Align::newPAHMessage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHMessage); connect(alignProcess.get(), &Ekos::Align::PAHEnabled, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHEnabled); connect(alignProcess.get(), &Ekos::Align::newImage, [&](FITSView * view) { ekosLiveClient.get()->media()->sendPreviewImage(view, QString()); }); connect(alignProcess.get(), &Ekos::Align::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendUpdatedFrame); connect(alignProcess.get(), &Ekos::Align::polarResultUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::setPolarResults); connect(alignProcess.get(), &Ekos::Align::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendAlignSettings); connect(alignProcess.get(), &Ekos::Align::newCorrectionVector, ekosLiveClient.get()->media(), &EkosLive::Media::setCorrectionVector); } } void Manager::setEkosLiveConnected(bool enabled) { ekosLiveClient.get()->setConnected(enabled); } void Manager::setEkosLiveConfig(bool onlineService, bool rememberCredentials, bool autoConnect) { ekosLiveClient.get()->setConfig(onlineService, rememberCredentials, autoConnect); } void Manager::setEkosLiveUser(const QString &username, const QString &password) { ekosLiveClient.get()->setUser(username, password); } bool Manager::ekosLiveStatus() { return ekosLiveClient.get()->isConnected(); } void Manager::syncActiveDevices() { for (auto oneDevice : genericDevices) { // Find out what ACTIVE_DEVICES properties this driver needs // and update it from the existing drivers. ITextVectorProperty *tvp = oneDevice->getBaseDevice()->getText("ACTIVE_DEVICES"); if (tvp) { //bool propertyUpdated = false; for (int i = 0; i < tvp->ntp; i++) { QList devs; if (!strcmp(tvp->tp[i].name, "ACTIVE_TELESCOPE")) { devs = findDevicesByInterface(INDI::BaseDevice::TELESCOPE_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_DOME")) { devs = findDevicesByInterface(INDI::BaseDevice::DOME_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_GPS")) { devs = findDevicesByInterface(INDI::BaseDevice::GPS_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_FILTER")) { if (tvp->tp[i].aux0 != nullptr) { bool *override = static_cast(tvp->tp[i].aux0); if (override && *override) continue; } devs = findDevicesByInterface(INDI::BaseDevice::FILTER_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_WEATHER")) { devs = findDevicesByInterface(INDI::BaseDevice::WEATHER_INTERFACE); } if (!devs.empty()) { if (tvp->tp[i].text != devs.first()->getDeviceName()) { //propertyUpdated = true; IUSaveText(&tvp->tp[i], devs.first()->getDeviceName().toLatin1().constData()); oneDevice->getDriverInfo()->getClientManager()->sendNewText(tvp); } } } // Save configuration // if (propertyUpdated) // oneDevice->setConfig(SAVE_CONFIG); } } } bool Manager::checkUniqueBinaryDriver(DriverInfo *primaryDriver, DriverInfo *secondaryDriver) { if (!primaryDriver || !secondaryDriver) return false; return (primaryDriver->getExecutable() == secondaryDriver->getExecutable() && primaryDriver->getAuxInfo().value("mdpd", false).toBool() == true); } void Manager::restartDriver(const QString &deviceName) { if (m_LocalMode) { for (auto &device : managedDevices) { if (QString(device->getDeviceName()) == deviceName) { DriverManager::Instance()->restartDriver(device->getDriverInfo()); break; } } } else INDI::WebManager::restartDriver(currentProfile, deviceName); } } diff --git a/kstars/kstars.kcfg b/kstars/kstars.kcfg index 9169029fb..2d0026e72 100644 --- a/kstars/kstars.kcfg +++ b/kstars/kstars.kcfg @@ -1,2540 +1,2544 @@ ksutils.h The screen coordinates of the Time InfoBox. QPoint(0,0) The screen coordinates of the Focus InfoBox. QPoint(600,0) The screen coordinates of the Geographic Location InfoBox. QPoint(0,600) If true, the Time InfoBox will show only its top line of data. true If true, the Focus InfoBox will show only its top line of data. true If true, the Geographic Location InfoBox will show only its top line of data. true Toggles display of all three InfoBoxes. true Toggles display of the Time InfoBox. true Toggles display of the Focus InfoBox. true Toggles display of the Geographic Location InfoBox. true Is the Time InfoBox anchored to a window edge? 0 = not anchored; 1 = anchored to right edge; 2 = anchored to bottom edge; 3 = anchored to bottom and right edges. 0 0 3 Is the Focus InfoBox anchored to a window edge? 0 = not anchored; 1 = anchored to right edge; 2 = anchored to bottom edge; 3 = anchored to bottom and right edges. 1 0 3 Is the Geographic Location InfoBox anchored to a window edge? 0 = not anchored; 1 = anchored to right edge; 2 = anchored to bottom edge; 3 = anchored to bottom and right edges. 2 0 3 Toggle display of the status bar. true Toggle display of the Horizontal coordinates of the mouse cursor in the status bar. true Toggle display of the Equatorial coordinates of the mouse cursor at the current epoch in the status bar. true Toggle display of the Equatorial coordinates of the mouse cursor at the standard epoch in the status bar. false true 1024 768 true Black Body List of the filenames of custom object catalogs. List of integers toggling display of each custom object catalog (any nonzero value indicates the objects in that catalog will be displayed). List of names for which custom catalogs are to be displayed. Names of objects entered into the find dialog are resolved using online services and stored in the database. This option also toggles the display of such resolved objects on the sky map. true 800 600 true true false Toggle display of crosshairs centered at telescope's pointed position in the KStars sky map. true Toggle display of INDI messages in the KStars statusbar. true false Show INDI messages as desktop notifications instead of dialogs. false true false false The default location of saved FITS files KSUtils::getDefaultPath("fitsDir") INDI server will attempt to bind with ports starting from this port 7624 INDI server will attempt to bind with ports ending with this port 9000 List of the aliases for filter wheel slots. PATH to indiserver binary KSUtils::getDefaultPath("indiServer") PATH to indihub-agent binary KSUtils::getDefaultPath("INDIHubAgent") false PATH to indi drivers directory KSUtils::getDefaultPath("indiDriversDir") false 320 240 false false false false false false false false false false false The City name of the current geographic location. Greenwich The Province name of the current geographic location. This is the name of the state for locations in the U. S. The Country name of the current geographic location. United Kingdom The longitude of the current geographic location, in decimal degrees. 0.0 The latitude of the current geographic location, in decimal degrees. 51.468 -10.0 0.0 Two-letter code that determines the dates on which daylight savings time begins and ends (you can view the rules by pressing the "Explain DST Rules" button in the Geographic Location window). -- If true, focus changes will cause the sky to visibly spin to the new position. Otherwise, the display will "snap" instantly to the new position. true If true, clicking on the skymap will select the closest object and highlights it. false Type of cursor when exploring the sky map. 1 The names of the currently selected field-of-view indicators. The list of defined FOV indicator names is listed in the "Settings|FOV Symbols" menu. Telrad If true, trails attached to solar system bodies will fade into the background sky color. true The right ascension of the initial focus position of the sky map, in decimal hours. This value is volatile; it is reset whenever the program shuts down. 180.0 The declination of the initial focus position of the sky map, in decimal degrees. This value is volatile; it is reset whenever the program shuts down. 45.0 The name of the object that should be centered and tracked on startup. If no object should be centered, set to "nothing". This value is volatile; it is reset whenever the program shuts down. nothing True if the skymap should track on its initial position on startup. This value is volatile; it is reset whenever the program shuts down. false Toggle whether KStars should hide some objects while the display is moving, for smoother motion. true Toggle whether constellation boundaries are hidden while the display is in motion. true Toggle whether constellation lines are hidden while the display is in motion. false Choose sky culture. 11 Toggle whether constellation names are hidden while the display is in motion. false Toggle whether the coordinate grids are hidden while the display is in motion. true Toggle whether the Milky Way contour is hidden while the display is in motion. true Toggle whether IC objects are hidden while the display is in motion. true Toggle whether Messier objects are hidden while the display is in motion. true Toggle whether NGC objects are hidden while the display is in motion. true Toggle whether extra objects are hidden while the display is in motion. true Toggle whether solar system objects are hidden while the display is in motion. false Toggle whether faint stars are hidden while the display is in motion. true Toggle whether name labels are hidden while the display is in motion. true Toggle whether asteroids are drawn in the sky map. true Toggle whether asteroid name labels are drawn in the sky map. false true Toggle whether comets are drawn in the sky map. true Toggle whether comet comas are drawn in the sky map. true Toggle whether comet name labels are drawn in the sky map. false Toggle whether supernovae are drawn in the sky map. false Toggle whether supernova name labels are drawn in the sky map. false Set magnitude limit for supernovae to be shown on the skymap. 16 Toggle supernova alerts. true Set magnitude limit for supernovae to be alerted. 13 Toggle whether constellation boundaries are drawn in the sky map. false Toggle whether constellation boundary containing the central focus point is highlighted in the sky map. false Toggle whether constellation lines are drawn in the sky map. false Toggle whether constellation art drawn in the sky map. false Toggle whether constellation name labels are drawn in the sky map. false Toggle whether deep-sky objects are drawn in the sky map. true Toggle whether the ecliptic line is drawn in the sky map. false Toggle whether the equator line is drawn in the sky map. false Coordinate grids will automatically change according to active coordinate system. true Toggle whether the equatorial coordinate grid is drawn in the sky map. false Toggle whether the horizontal coordinate grid is drawn in the sky map. false Toggle whether the local meridian line is drawn in the sky map. false Toggle whether the region below the horizon is opaque. true Toggle whether the horizon line is drawn in the sky map. true Toggle whether flags are drawn in the sky map. true Toggle whether IC objects are drawn in the sky map. false Toggle whether NGC objects are drawn in the sky map. true Toggle whether Messier objects are drawn in the sky map. true Toggle whether Messier objects are rendered as images in the sky map. true Toggle whether extra objects are drawn in the sky map. true Toggle whether the Milky Way contour is drawn in the sky map. true Toggle whether the Milky Way contour is filled. When this option is false, the Milky Way is shown as an outline. true Meta-option to control whether all major planets (and the Sun and Moon) are drawn in the sky map. true Toggle whether major planets (and the Sun and Moon) are rendered as images in the sky map. true Toggle whether major planets (and the Sun and Moon) are labeled in the sky map. true Toggle whether the Sun is drawn in the sky map. true Toggle whether the Moon is drawn in the sky map. true Toggle whether Mercury is drawn in the sky map. true Toggle whether Venus is drawn in the sky map. true Toggle whether Mars is drawn in the sky map. true Toggle whether Jupiter is drawn in the sky map. true Toggle whether Saturn is drawn in the sky map. true Toggle whether Uranus is drawn in the sky map. true Toggle whether Neptune is drawn in the sky map. true Toggle whether Pluto is drawn in the sky map. true Toggle whether stars are drawn in the sky map. true Toggle whether star magnitude (brightness) labels are shown in the sky map. false Toggle whether star name labels are shown in the sky map. true Toggle whether deep-sky object magnitude (brightness) labels are shown in the sky map. false Toggle whether deep-sky object name labels are shown in the sky map. false The timescale above which slewing mode is forced on at all times. 60 The background fill mode for the on-screen information boxes: 0="no BG"; 1="semi-transparent BG"; 2="opaque BG" 1 Algorithm for the mapping projection. 0 Use official IAU abbreviations for constellation names. false Use Latin constellation names. false Use localized constellation names (if localized names are not available, default to Latin names). true Display the sky with horizontal coordinates (when false, equatorial coordinates will be used). true Toggle whether a centered object automatically gets a name label attached. true Toggle whether a centered solar system object automatically gets a trail attached, as long as it remains centered. true Toggle whether the object under the mouse cursor gets a transient name label. true Toggle whether object positions are corrected for the effects of atmospheric refraction (only applies when horizontal coordinates are used). true Toggle whether corrections due to bending of light around the sun are taken into account false Toggle whether the sky is rendered using antialiasing. Lines and shapes are smoother with antialiasing, but rendering the screen will take more time. true The zoom level, measured in pixels per radian. 250. 250. 5000000. When zooming in or out, change zoom speed factor by this multiplier. 0.2 0.01 1.0 The faint magnitude limit for drawing asteroids. 15.0 The maximum magnitude (visibility) to filter the asteroid data download from JPL. 12.000 Controls the relative number of asteroid name labels drawn in the map. 4.0 The faint magnitude limit for drawing deep-sky objects, when fully zoomed in. 16.0 The faint magnitude limit for drawing deep-sky objects, when fully zoomed out. 5.0 When enabled, objects whose magnitudes are unknown, or not available to KStars, are drawn irrespective of the faint limits set. true Sets the density of stars in the field of view 5 The faint magnitude limit for drawing stars, when the map is in motion (only applicable if faint stars are set to be hidden while the map is in motion). 5.0 The relative density for drawing star name and magnitude labels. 2.0 The relative density for drawing deep-sky object name and magnitude labels. 5.0 If true, long names (common names) for deep-sky objects are shown in the labels. false The maximum solar distance for drawing comets. 3.0 Use experimental OpenGL backend (deprecated). false The state of the clock (running or not) true Objects in the observing list will be highlighted with a symbol in the map. true Objects in the observing list will be highlighted with a colored name label in the map. false The observing list will prefer DSS imagery while downloading imagery. true The observing list will prefer SDSS imagery while downloading imagery. false Check this if you use a large Dobsonian telescope. Sorting by percentage current altitude is an easy way of determining what objects are well-placed for observation. However, when using a large Dobsonian telescope, objects close to the zenith are hard to observe. Since tracking there corresponds to a rotation in azimuth, it is both counterintuitive and requires the observer to frequently move the ladder. The region around the zenith where this is particularly frustrating is called the Dobsonian hole. This checkbox makes the observing list consider objects present in the hole as unfit for observation. false This specifies the angular radius of the Dobsonian hole, i.e. the region where a large Dobsonian telescope cannot be pointed easily. 15.00 40.00 The name of the color scheme moonless-night.colors The method for rendering stars: 0="realistic colors"; 1="solid red"; 2="solid black"; 3="solid white"; 4="solid real colors" 0 4 The color saturation level of stars (only applicable when using "realistic colors" mode). 6 10 The color for the angular-distance measurement ruler. #FFF The background color of the on-screen information boxes. #000 The text color for the on-screen information boxes, when activated by a mouse click. #F00 The normal text color of the on-screen information boxes. #FFF The color for the constellation boundary lines. #222 The color for the constellation boundary lines. #222 The color for the constellation figure lines. #555 The color for the constellation names. #AA7 The color for the cardinal compass point labels. #002 The color for the ecliptic line. #663 The color for the equator line. #FFF The color for the equatorial coordinate grid lines. #456 The color for the horizontal coordinate grid lines. #5A3 The color for objects which have extra URL links available. #A00 The color for the horizon line and opaque ground. #5A3 The color for the local meridian line. #0059b3 The color for Messier object symbols. #0F0 The color for NGC object symbols. #066 The color for IC object symbols. #439 The color for the Milky Way contour. #123 The color for star name labels. #7AA The color for deep-sky object name labels. #7AA The color for solar system object labels. #439 The color for solar system object trails. #963 The color for the sky background. #002 The color for the artificial horizon region. #C82828 The color for telescope target symbols. #8B8 Color of visible satellites. #00FF00 Color of invisible satellites. #FF0000 Color of satellites labels. #640000 Color of supernova #FFA500 The color for user-added object labels. #439 The color for RA Guide Error bar in Ekos guide module. #00FF00 The color for DEC Guide Error bar in Ekos guide module. #00A5FF The color for solver FOV box in Ekos alignment module. #FFFF00 false Xplanet binary path KSUtils::getDefaultPath("XplanetPath") Option to use a FIFO file instead of saving to the hard disk true How long to wait for XPlanet before giving up in milliseconds 1000 How long to pause between frames in the XPlanet Animation 100 Width of xplanet window 640 Height of xplanet window 480 If true, display a label in the upper right corner. false Show local time. true Show GMT instead of local time. false Specify the text of the first line of the label. By default, it says something like "Looking at Earth". Any instances of %t will be replaced by the target name, and any instances of %o will be replaced by the origin name. Specify the point size. 12 Set the color for the label. #F00 Specify the format for the date/time label. This format string is passed to strftime(3). The default is "%c %Z", which shows the date, time, and time zone in the locale’s appropriate date and time representation. %c %Z false true false false Draw a glare around the sun with a radius of the specified value larger than the Sun. The default value is 28. 28 Place the observer above a random latitude and longitude false Place the observer above the specified longitude and latitude true Render the target body as seen from above the specified latitude (in degrees). The default value is 0. 0 Place the observer above the specified longitude (in degrees). Longitude is positive going east, negative going west (for the earth and moon), so for example Los Angeles is at -118 or 242. The default value is 0. 0 The default is no projection. Multiple bodies will not be shown if this option is specified, although shadows will still be drawn. 0 Use a file as the background image, with the planet to be superimposed upon it. This option is only meaningful with the -projection option. A color may also be supplied. false Use a file as the background image. false The path of the background image. Use a color as the background. true The color of the background. #000 A star of the specified magnitude will have a pixel brightness of 1. The default value is 10. Stars will be drawn more brightly if this number is larger. 10 If checked, use an arc file to be plotted against the background stars. false Specify an arc file to be plotted against the background stars. If checked, use a config file. false Use the specified configuration file. If checked, use kstars's FOV. false If checked, use the specified marker file. false Specify a file containing user-defined marker data to display against the background stars. If checked, write coordinates of the bounding box for each marker in a file. false Write coordinates of the bounding box for each marker to this file. If checked, use star map file to draw the background stars. false Star map file path This option is only used when creating JPEG images. The quality can range from 0 to 100. The default value is 80. 80 Toggle whether satellite tracks are drawn in the sky map. false Toggle whether satellite tracks are drawn in the sky map. false If selected, satellites will be draw like stars, otherwise, draw satellites as small colored square. false Toggle whether satellite labels are drawn in the sky map. false List of selected satellites. Checking this option causes recomputation of current equatorial coordinates from catalog coordinates (i.e. application of precession, nutation and aberration corrections) for every redraw of the map. This makes processing slower when there are many stars to handle, but is more likely to be bug free. There are known bugs in the rendering of stars when this recomputation is avoided. false The default size for DSS images downloaded from the Internet. 15.0 To include parts of the star field, we add some extra padding around DSS images of deep-sky objects. This option configures the total (both sides) padding added to either dimension of the field. 10.0 Checking this option causes KStars to generate verbose debug information for diagnostic purposes. This may cause slowdown of KStars. false Checking this option causes KStars to generate regular debug information. true Checking this option causes KStars to stop generating ANY debug information. false Checking this option causes KStars log debug messages to the default output used by the platform (e.g. Standard Error). true Checking this option causes KStars log debug messages to a log file as specified. false Log FITS Data activity. false Log INDI devices activity. false Log Ekos Capture Module activity. false Log Ekos Focus Module activity. false Log Ekos Guide Module activity. false Log Ekos Alignment Module activity. false Log Ekos Mount Module activity. false Log Ekos Observatory Module activity. false true Display all captured FITS images in a single tab instead of multiple tabs per image. true Display all captured FITS images in a single FITS Viewer window. By default each camera create its own FITS Viewer instance false Display all opened FITS images in a single FITS Viewer window. true Bring the FITSViewer window to the foreground when receiving a new image. true false !KSUtils::isHardwareLimited() false !KSUtils::isHardwareLimited() !KSUtils::isHardwareLimited() KSUtils::isHardwareLimited() 4 false false 40.0 0 600 600 true false true true true false Simulators false true false true 1 Minimum telescope altitude limit. If the telescope is below this limit, it will be commanded to stop. 0 Maximum telescope altitude limit. If the telescope is above this limit, it will be commanded to stop. 90.0 false If the target hour angle exceeds this value, Ekos will command a meridian flip and if successful it will resume guiding and capture operations. Maximum limit for the hour angle of the telescope. If the hour angle of the telescope is above this limit, a meridian flip will be forced. 90.0 false false true false false false 3:00 AM false 1 0 If guide deviation exceeds this limit, the exposure will be automatically aborted and only resumed when the deviation is within this limit. 2 If HFR deviation exceeds this limit, the autofocus routine will be automatically started. 0.5 false false false Sets the time interval before forced autofocus attempts during a capture sequence. 60 false If set, Ekos will capture a few flat images to determine the optimal exposure time to achieve the desired ADU value. 0 Maximum difference between measured and target ADU values to deem the value as acceptable. 1000 0 0 0 0 0.1 0 false false 2.5 true false 1 30 true !KSUtils::isHardwareLimited() !KSUtils::isHardwareLimited() true 0 KSUtils::getDefaultPath("fitsDir") 60 false false false Step size of the absolute focuser. The step size TICKS should be adjusted so that when the focuser moves TICKS steps, the difference in HFR is more than 0.1 pixels. Lower the value when you are close to optimal focus. 100 Wait for this many seconds after moving the focuser before capturing the next image during AutoFocus. 0 Wait for this many seconds after resuming guide. 0 The tolerance specifies the percentage difference between the current focusing position and the minimum obtained during the focusing run. Adjustment of this value is necessary to prevent the focusing algorithm from oscillating back and forth. 1 Set the maximum travel distance of an absolute focuser. 10000 Specifies gain value of CCD when performing focusing if supported by camera. 0 Set box size to select a focus star. 64 Set horizontal binning of CCD camera while in focus mode. 1 Set vertical binning of CCD camera while in focus mode. 1 true false During full field focusing, stars which are inside this percentage of the frame are filtered out of HFR calculation (default 0%). Detection algorithms may also have an inherent filter. 0.0 During full field focusing, stars which are outside this percentage of the frame are filtered out of HFR calculation (default 100%). Detection algorithms may also have an inherent filter. 100.0 false true false 0 150 0 0 1 100000 1 1.5 5 3 Specifies exposure value of CCD in seconds when performing plate solving. 1 Set binning index of CCD camera while in alignment mode. Default values 0-3 corresponding to 1x1 to 4x4 binning. 4 is max binning. 4 Use rotator when performing load and slew. false Threshold between measured and FITS position angles in arcminutes to consider the load and slew operation successful. 30 Solver backend (0 ASTAP, 1 astrometry.net). 1 0 0 1 true false false 30 0 false 1500 false true true true true 1 true 2 true true true 30 false Path to astrometry.net solver location. KSUtils::getDefaultPath("AstrometrySolverBinary") false Path to astrometry.net wcsinfo location. KSUtils::getDefaultPath("AstrometryWCSInfo") false Path to astrometry.net file location. KSUtils::getDefaultPath("AstrometryConfFile") true true Folder in which the desired python executable or link to be used for astrometry.net resides. /usr/local/opt/python/libexec/bin false false Path to the Sextractor executable. KSUtils::getDefaultPath("SextractorBinary") Key to access astrometry.net online web services. You must register with astrometry.net to obtain a key. iczikaqstszeptgs http://nova.astrometry.net true 180 -1 true false 1.0 0 0 localhost 4400 localhost 5656 0 1000 2 false 1 false false 3 60 10 true false + + + true + false false 2 1 0 1 45 10 500 false false false 2 true true true true true true 133.33 133.33 0 0 0 0 5000 5000 100 100 0.5 2 true true false false Log Ekos Scheduler Module activity. false Sort scheduler jobs by priority and altitude. true true false false false false true false 2 true 5 30 3 0 0 0 0 0 0 0 0 1 0 false 7624 8624 300 1000 None false false false Toggle whether the HIPS sources are drawn in the sky map. false true true false false true 600 true true true 30 true true true true KSUtils::getDefaultPath("ASTAP") false 0 true 30 false false