diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ PROJECT(kstars CXX C) set (KStars_VERSION_MAJOR 3) set (KStars_VERSION_MINOR 2) -set (KStars_VERSION_REVISION 0) +set (KStars_VERSION_REVISION 1) set (CMAKE_CXX_STANDARD 11) # Build KStars Lite with -DBUILD_KSTARS_LITE=ON diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,82 @@ $ git log --date=short --pretty=format:"%h %ad %<(20)%an %<(150,trunc)%s" +3.2.0 (Ivy): + +596569b5a 2019-04-18 Jasem Mutlaq Including missing header +63a05b9f1 2019-04-14 Jasem Mutlaq Must add mount this way since we have a tree structure +18fdbc7f3 2019-04-14 Wolfgang Reissenberger Capture counting corrected +49fc6bfc1 2019-04-13 Jasem Mutlaq Do not connect to EkosLive with empty username +8968926f9 2019-04-11 Jasem Mutlaq Also accept .fits.fz in FITSViewer +5577dc1c6 2019-04-09 Jasem Mutlaq Fix slew and sync in KStars Lite. Patch by Sebastian. +060749c18 2019-04-08 Wolfgang Reissenberger Clean module registration +11cc7c78c 2019-04-07 Jasem Mutlaq Use click to trigger the change since the signals are attached to the button clicked event +6fa80f617 2019-04-06 Jasem Mutlaq Show toolip explicitly on button click +29461e124 2019-04-06 Jasem Mutlaq Add information icon to explain what the guiding rate control is for +0c8f1ef98 2019-04-06 Jasem Mutlaq Resume KStars if paused in EkosLive +4389e2361 2019-04-06 Jasem Mutlaq Make sure clock is ticking and sync with system time when websocket connection is established +c90eaa335 2019-04-06 Jasem Mutlaq Move auto star selection for calibration to main guide module to make it more accessible +8c0e7b2b9 2019-04-06 Jasem Mutlaq Fix issue with online solver args +7bf777cf4 2019-04-04 Jasem Mutlaq Add RC telescope type +53266b534 2019-04-04 Jasem Mutlaq Fix capture delay when using legacy polar alignment tool +9d6687356 2019-04-03 Jasem Mutlaq If primary or guide IDs are zero or less, set profile scope to Default +b91610734 2019-04-02 Jasem Mutlaq Add support to updating scopes and profiles +ae65feaa1 2019-04-02 Jasem Mutlaq Scope commands do not require active connection in this location as well +e90132500 2019-04-02 Jasem Mutlaq Scope commands do not require active connection +049f3083e 2019-04-02 Jasem Mutlaq GET_SCOPES can now be sent before Ekos is online +12a340798 2019-04-02 Jasem Mutlaq Add scope websocket functions to add and delete scopes. Improve getScopes to cover all available telescopes +d6e0f59b5 2019-04-01 Jasem Mutlaq Send profile add deleting or adding one +c59abae19 2019-04-01 Wolfgang Reissenberger Unchecking the meridian flip checkbox reset the meridian flip state +e8cf401fb 2019-03-31 Jasem Mutlaq Move send drivers before the block requiring live ekos connection +9bfe7a47b 2019-03-31 Jasem Mutlaq Do not change filter directly, use the filter manager +25441353f 2019-03-31 Jasem Mutlaq Fix infobox setting not sticking for some reason under Qt 5.12 +5689e1085 2019-03-30 Jasem Mutlaq Save focus module filter wheel and filter in options +87baf5eb9 2019-03-27 Eric Dejouhanet Fix altitude restriction log and allow restriction down to -15 degrees +9895d4898 2019-03-24 Jasem Mutlaq Wait until backlash builds up before moving to more aggressive pulses that might cause overshooting the target position +3916fc78e 2019-03-24 Jasem Mutlaq Add debug log for remaining angle for mount rotation in alignment module +c5f80470d 2019-03-21 Jasem Mutlaq Add websocket function to get all drivers +f9ab9cbb8 2019-03-18 Jasem Mutlaq Update docs +677f7dbad 2019-03-18 Jasem Mutlaq Profile management via websocket is ready for testing +60bd7f39a 2019-03-18 Jasem Mutlaq Initial work for profile and driver exposure to ekoslive +bd1d4d28b 2019-03-18 Wolfgang Reissenberger Bugfix: proper usage of abort() for finishing a capture sequence queue +abbfb7db1 2019-03-17 Jasem Mutlaq Reduce calibration iterations to 3 by default as it is mostly what is required for most cases +fabf80109 2019-03-17 Eric Dejouhanet Prevent rescheduling aborted jobs until all jobs are processed +410f0226f 2019-03-17 Jasem Mutlaq Making Scheduler robust against guiding problems +f52ef343d 2019-03-17 Wolfgang Reissenberger Bugfix #405325: Keeping meridian flip in capture and mount in sync. +7a04a4393 2019-03-12 Jasem Mutlaq Fix websocket getting called from a different thread +cba93a705 2019-03-12 Jasem Mutlaq Add EkosLive DBus methods and properties +99ba5e31e 2019-03-12 Jasem Mutlaq Display saved DSLR infos +15c296062 2019-03-12 Robert Lancaster Making the StarProfile Viewer fix from 3.1.0 work for Guide and Focus Views too +6ee0ebfb7 2019-03-10 Jasem Mutlaq Load captured RAW images in summary without have to double-covert them since they are converted once in INDI::CCD +1549005f5 2019-03-10 Jasem Mutlaq Only remove .fits explicitly from base name instead of finding last decimal point since this causes conflicts with exposures in file names +08fc70c84 2019-03-09 Jasem Mutlaq Make the mount window resizable to fit on different DPIs +6b3d3b4fb 2019-03-09 Jasem Mutlaq HEAD is at v3.2.0 now +ff94934ee 2019-03-09 Jasem Mutlaq INDI Driver auto sync +4cbaa0f58 2019-03-08 Jasem Mutlaq Comment out obsolete slot connect +f250da377 2019-03-07 Jasem Mutlaq Remove unrequired attributes. +04dd9fed1 2019-03-07 Jasem Mutlaq Notify user of error in focusing even when not in autofocus +b10c35a93 2019-03-07 Jasem Mutlaq Add support to sending horizontal coordinates from the mount box +cfdecf66f 2019-03-06 Heiko Becker LONG_LONG_MAX is an obsolete GNU name for LLONG_MAX +07219cd80 2019-03-06 Jasem Mutlaq Improve description of the ImageToFITS option +7d0e57198 2019-03-06 Jasem Mutlaq Refactor the variable means to remove any confusion +a4f962162 2019-03-05 Jasem Mutlaq Auto ImageToFITS should be false by default +1da8d1b62 2019-03-05 Jasem Mutlaq Improve behavior for dark and flat frames capture when it comes to covering the mount +c1aef273e 2019-03-05 Jasem Mutlaq Support converting images to FITS +efd36250e 2019-03-04 Jasem Mutlaq Before loading next image, check if current image is temporary and if yes remove it from disk +61d42b6e4 2019-03-04 Jasem Mutlaq Add notification dialog for user to cover scope with flat field light source in case of manual source +9947d6ff0 2019-03-04 Yuri Chornoivan Fix minor typos +efa95bffb 2019-03-04 Robert Lancaster Making the initial astrometry.net path change automatic +336babded 2019-03-03 Jasem Mutlaq Configure Equipment was causing a conflict on Mac as found by Robert. Broke the menu and distributed the two items to where they logically belong +f93400f75 2019-03-03 Jasem Mutlaq More PHD2 fixes: 1. Restore star display despite "Receive External Guide Frame" setting since they are two separate options. 2. Fix crash when async.. +f8522bfb1 2019-03-03 Jasem Mutlaq Be silent when auto updating comet and asteroid orbital elements +1c9157e20 2019-03-03 Jasem Mutlaq Add comet and asteroid orbital elements auto update on startup +d1b81599a 2019-03-01 Jasem Mutlaq Make selection behavior single rows for the capture jobs +cf581270c 2019-02-28 Wolfgang Reissenberger Deleting arbitrary imaging sequence in the Capture module corrected. +3a81f2e51 2019-02-27 Jasem Mutlaq Fix upload issues to the cloud with compressed images +dc198425f 2019-02-27 Jasem Mutlaq All compressed files are extracted and considered temporary however we need to know if the original compressed file was temporary or not +8e3b17c32 2019-02-27 Jasem Mutlaq Fix state of ekoslive button toggle on startup +0c70fad89 2019-02-27 Jasem Mutlaq Do not exist program on error, return error code + 3.1.0 (Timmy): 5af4cbdc7 2019-02-26 Jasem Mutlaq Changes for KStars 3.1.0 diff --git a/README b/README --- a/README +++ b/README @@ -47,7 +47,7 @@ The KStars handbook can be found in your $(KDEDIR)/share/doc/HTML//kstars/ directory. You can also easily access it from the Help menu, or by pressing -the [F1] key, or by visiting http://edu.kde.org/kstars/handbook/ +the [F1] key, or by visiting https://docs.kde.org/?application=kstars In addition, there are the following README files: diff --git a/cmake/modules/COPYING-CMAKE-SCRIPTS b/cmake/modules/COPYING-CMAKE-SCRIPTS deleted file mode 100644 --- a/cmake/modules/COPYING-CMAKE-SCRIPTS +++ /dev/null @@ -1,22 +0,0 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmake/modules/FindAstrometryNet.cmake b/cmake/modules/FindAstrometryNet.cmake --- a/cmake/modules/FindAstrometryNet.cmake +++ b/cmake/modules/FindAstrometryNet.cmake @@ -7,8 +7,28 @@ # Copyright (c) 2016, Jasem Mutlaq # Based on FindLibfacile by Carsten Niehaus, # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (ASTROMETRYNET_EXECUTABLE) diff --git a/cmake/modules/FindCFitsio.cmake b/cmake/modules/FindCFitsio.cmake --- a/cmake/modules/FindCFitsio.cmake +++ b/cmake/modules/FindCFitsio.cmake @@ -8,8 +8,28 @@ # Copyright (c) 2006, Jasem Mutlaq # Based on FindLibfacile by Carsten Niehaus, # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (CFITSIO_INCLUDE_DIR AND CFITSIO_LIBRARIES) diff --git a/cmake/modules/FindEigen3.cmake b/cmake/modules/FindEigen3.cmake --- a/cmake/modules/FindEigen3.cmake +++ b/cmake/modules/FindEigen3.cmake @@ -13,7 +13,29 @@ # Copyright (c) 2006, 2007 Montel Laurent, # Copyright (c) 2008, 2009 Gael Guennebaud, # Copyright (c) 2009 Benoit Jacob -# Redistribution and use is allowed according to the terms of the 2-clause BSD license. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if(NOT Eigen3_FIND_VERSION) if(NOT Eigen3_FIND_VERSION_MAJOR) diff --git a/cmake/modules/FindINDI.cmake b/cmake/modules/FindINDI.cmake --- a/cmake/modules/FindINDI.cmake +++ b/cmake/modules/FindINDI.cmake @@ -14,7 +14,28 @@ # Copyright (c) 2012, Pino Toscano # Based on FindLibfacile by Carsten Niehaus, # -# Redistribution AND use is allowed according to the terms of the BSD license. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. macro(_INDI_check_version) file(READ "${INDI_INCLUDE_DIR}/indiapi.h" _INDI_version_header) diff --git a/cmake/modules/FindLibRaw.cmake b/cmake/modules/FindLibRaw.cmake --- a/cmake/modules/FindLibRaw.cmake +++ b/cmake/modules/FindLibRaw.cmake @@ -11,8 +11,28 @@ # Copyright (c) 2013, Pino Toscano # Copyright (c) 2013, Gilles Caulier # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. FIND_PACKAGE(PkgConfig) diff --git a/cmake/modules/FindNova.cmake b/cmake/modules/FindNova.cmake --- a/cmake/modules/FindNova.cmake +++ b/cmake/modules/FindNova.cmake @@ -8,8 +8,28 @@ # Copyright (c) 2006, Jasem Mutlaq # Based on FindLibfacile by Carsten Niehaus, # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (NOVA_INCLUDE_DIR AND NOVA_LIBRARIES) diff --git a/cmake/modules/FindWCSLIB.cmake b/cmake/modules/FindWCSLIB.cmake --- a/cmake/modules/FindWCSLIB.cmake +++ b/cmake/modules/FindWCSLIB.cmake @@ -8,8 +8,28 @@ # Copyright (c) 2006, Jasem Mutlaq # Based on FindLibfacile by Carsten Niehaus, # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (WCSLIB_INCLUDE_DIR AND WCSLIB_LIBRARIES) diff --git a/cmake/modules/FindXplanet.cmake b/cmake/modules/FindXplanet.cmake --- a/cmake/modules/FindXplanet.cmake +++ b/cmake/modules/FindXplanet.cmake @@ -5,8 +5,29 @@ # XPLANET_EXECUTABLE - the XPlanet executable # # Copyright (c) 2008, Jerome SONRIER, -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # THIS FILE NEEDS IMPROVEMENTS # diff --git a/cmake/modules/MacroBoolTo01.cmake b/cmake/modules/MacroBoolTo01.cmake --- a/cmake/modules/MacroBoolTo01.cmake +++ b/cmake/modules/MacroBoolTo01.cmake @@ -5,8 +5,29 @@ # # Copyright (c) 2006, Alexander Neundorf, # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + MACRO(MACRO_BOOL_TO_01 FOUND_VAR ) diff --git a/kstars/auxiliary/geolocation.cpp b/kstars/auxiliary/geolocation.cpp --- a/kstars/auxiliary/geolocation.cpp +++ b/kstars/auxiliary/geolocation.cpp @@ -102,8 +102,8 @@ QString GeoLocation::translatedProvince() const { return Province.isEmpty() ? - QString() : - i18nc(QString("Region/state in " + country()).toUtf8().data(), Province.toUtf8().data()); + QString() : + i18nc(QString("Region/state in " + country()).toUtf8().data(), Province.toUtf8().data()); } QString GeoLocation::translatedCountry() const @@ -130,7 +130,7 @@ lat1 = latd; xn = axis / (sqrt(1 - e2 * s1 * s1)); - latd = atan2((long double)rpro * (1 + e2 * xn * s1), PosCartZ); + latd = atan2(static_cast(rpro) * (1 + e2 * xn * s1), PosCartZ); } sinl = sin(latd); diff --git a/kstars/auxiliary/ksuserdb.cpp b/kstars/auxiliary/ksuserdb.cpp --- a/kstars/auxiliary/ksuserdb.cpp +++ b/kstars/auxiliary/ksuserdb.cpp @@ -672,16 +672,16 @@ /* HiPS Section */ -void KSUserDB::AddHIPSSource(const QMap &oneSource) +void KSUserDB::AddHIPSSource(const QMap &oneSource) { userdb_.open(); QSqlTableModel HIPSSource(nullptr, userdb_); HIPSSource.setTable("hips"); HIPSSource.select(); QSqlRecord record = HIPSSource.record(); - for (QMap::const_iterator iter = oneSource.begin(); iter != oneSource.end(); ++iter) + for (QMap::const_iterator iter = oneSource.begin(); iter != oneSource.end(); ++iter) record.setValue(iter.key(), iter.value()); HIPSSource.insertRecord(-1, record); @@ -719,7 +719,7 @@ for (int i = 0; i < HIPSSource.rowCount(); ++i) { - QMap recordMap; + QMap recordMap; QSqlRecord record = HIPSSource.record(i); for (int j = 1; j < record.count(); j++) recordMap[record.fieldName(j)] = record.value(j).toString(); @@ -733,16 +733,16 @@ /* DSLR Section */ -void KSUserDB::AddDSLRInfo(const QMap &oneInfo) +void KSUserDB::AddDSLRInfo(const QMap &oneInfo) { userdb_.open(); QSqlTableModel DSLRInfo(nullptr, userdb_); DSLRInfo.setTable("dslr"); DSLRInfo.select(); QSqlRecord record = DSLRInfo.record(); - for (QMap::const_iterator iter = oneInfo.begin(); iter != oneInfo.end(); ++iter) + for (QMap::const_iterator iter = oneInfo.begin(); iter != oneInfo.end(); ++iter) record.setValue(iter.key(), iter.value()); DSLRInfo.insertRecord(-1, record); @@ -795,7 +795,7 @@ for (int i = 0; i < DSLRInfo.rowCount(); ++i) { - QMap recordMap; + QMap recordMap; QSqlRecord record = DSLRInfo.record(i); for (int j = 1; j < record.count(); j++) recordMap[record.fieldName(j)] = record.value(j); @@ -1246,7 +1246,7 @@ flag_file_sequence.append(qMakePair(QString("icon"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("label"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("color"), KSParser::D_QSTRING)); - KSParser flagparser(flagfilename,'#',flag_file_sequence,' '); + KSParser flagparser(flagfilename, '#', flag_file_sequence, ' '); QHash row_content; while (flagparser.HasNextRow()) @@ -1259,7 +1259,7 @@ QString label = row_content["label"].toString(); QString color = row_content["color"].toString(); - AddFlag(ra,dec,epoch,icon,label,color); + AddFlag(ra, dec, epoch, icon, label, color); } return true; } @@ -1371,9 +1371,9 @@ readScopes(); else if( reader_->name() == "eyepieces" ) readEyepieces(); - else if( reader_->name() =="lenses" ) + else if( reader_->name() == "lenses" ) readLenses(); - else if( reader_->name() =="filters" ) + else if( reader_->name() == "filters" ) readFilters(); } } @@ -1490,6 +1490,8 @@ type = "Kutter (Schiefspiegler)"; if (type == "C") type = "Cassegrain"; + if (type == "RC") + type = "Ritchey-Chretien"; } else if (reader_->name() == "focalLength") { diff --git a/kstars/data/indidrivers.xml b/kstars/data/indidrivers.xml --- a/kstars/data/indidrivers.xml +++ b/kstars/data/indidrivers.xml @@ -71,7 +71,7 @@ indi_lx200fs2 - 2.0 + 2.2 indi_lx200basic @@ -115,23 +115,23 @@ indi_ieq_telescope - 1.5 + 1.6 indi_ieq_telescope - 1.5 + 1.6 indi_ieq_telescope - 1.5 + 1.6 indi_ieq_telescope - 1.5 + 1.6 indi_ieq_telescope - 1.5 + 1.6 indi_lx200zeq25 @@ -155,11 +155,11 @@ indi_ioptronv3_telescope - 1.0 + 1.1 indi_ioptronv3_telescope - 1.0 + 1.1 indi_lx200pulsar2 @@ -217,7 +217,7 @@ indi_nstep_focus - 1.1 + 1.2 indi_moonlite_focus @@ -277,7 +277,7 @@ indi_sestosenso_focus - 1.1 + 1.2 indi_lakeside_focus @@ -314,6 +314,12 @@ 1.0 + + + indi_simulator_detector + 1.0 + + indi_xagyl_wheel @@ -329,19 +335,19 @@ indi_quantum_wheel - 0.1 + 0.3 indi_trutech_wheel 0.1 indi_qhycfw2_wheel - 0.1 + 1.2 indi_qhycfw3_wheel - 0.1 + 1.1 indi_manual_wheel @@ -463,6 +469,10 @@ indi_watcher_weather + 1.1 + + + indi_weather_safety_proxy 1.0 @@ -491,10 +501,16 @@ - + indi_apogee_ccd - 1.6 - + 1.7 + + + + + indi_apogee_wheel + 1.7 + @@ -740,7 +756,7 @@ indi_nexdome - 1.1 + 1.2 @@ -764,7 +780,7 @@ indi_qhy_ccd - 2.3 + 2.4 diff --git a/kstars/ekos/align/align.cpp b/kstars/ekos/align/align.cpp --- a/kstars/ekos/align/align.cpp +++ b/kstars/ekos/align/align.cpp @@ -60,7 +60,8 @@ // Sidereal rate, degrees/s const double Align::SIDRATE = 0.004178; -const QMap Align::PAHStages = { +const QMap Align::PAHStages = +{ {PAH_IDLE, I18N_NOOP("Idle")}, {PAH_FIRST_CAPTURE, I18N_NOOP("First Capture"}), {PAH_FIND_CP, I18N_NOOP("Finding CP"}), @@ -100,12 +101,12 @@ a->setEnabled(true); showFITSViewerB->setIcon( - QIcon::fromTheme("kstars_fitsviewer")); + QIcon::fromTheme("kstars_fitsviewer")); showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Align::showFITSViewer); toggleFullScreenB->setIcon( - QIcon::fromTheme("view-fullscreen")); + QIcon::fromTheme("view-fullscreen")); toggleFullScreenB->setShortcut(Qt::Key_F4); toggleFullScreenB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(toggleFullScreenB, &QPushButton::clicked, this, &Ekos::Align::toggleAlignWidgetFullScreen); @@ -127,47 +128,53 @@ // Effective FOV Edit connect(FOVOut, &QLineEdit::editingFinished, this, &Align::syncFOV); - connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Align::setDefaultCCD); + connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Align::setDefaultCCD); connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Align::checkCCD); connect(correctAltB, &QPushButton::clicked, this, &Ekos::Align::correctAltError); connect(correctAzB, &QPushButton::clicked, this, &Ekos::Align::correctAzError); - connect(loadSlewB, &QPushButton::clicked, [&]() { + connect(loadSlewB, &QPushButton::clicked, [&]() + { loadAndSlew(); }); FilterDevicesCombo->addItem("--"); connect(FilterDevicesCombo, static_cast(&QComboBox::activated), - [=](const QString &text) + [ = ](const QString & text) { syncSettings(); Options::setDefaultAlignFW(text); }); connect(FilterDevicesCombo, static_cast(&QComboBox::activated), this, &Ekos::Align::checkFilter); connect(FilterPosCombo, static_cast(&QComboBox::activated), - [=](int index) + [ = ](int index) { syncSettings(); Options::setLockAlignFilterIndex(index); } - ); + ); - connect(PAHSlewRateCombo, static_cast(&QComboBox::activated), [&](int index) { - Options::setPAHMountSpeedIndex(index); + connect(PAHSlewRateCombo, static_cast(&QComboBox::activated), [&](int index) + { + Options::setPAHMountSpeedIndex(index); }); gotoModeButtonGroup->setId(syncR, GOTO_SYNC); gotoModeButtonGroup->setId(slewR, GOTO_SLEW); gotoModeButtonGroup->setId(nothingR, GOTO_NOTHING); connect(gotoModeButtonGroup, static_cast(&QButtonGroup::buttonClicked), this, - [=](int id) { this->currentGotoMode = static_cast(id); }); + [ = ](int id) + { + this->currentGotoMode = static_cast(id); + }); m_CaptureTimer.setSingleShot(true); m_CaptureTimer.setInterval(10000); - connect(&m_CaptureTimer, &QTimer::timeout, [&]() { + connect(&m_CaptureTimer, &QTimer::timeout, [&]() + { if (m_CaptureTimeoutCounter++ > 3) { appendLogText(i18n("Capture timed out.")); @@ -179,7 +186,7 @@ if (targetChip->isCapturing()) { targetChip->abortExposure(); - m_CaptureTimer.start( m_CaptureTimer.interval()*2); + m_CaptureTimer.start( m_CaptureTimer.interval() * 2); } else captureAndSolve(); @@ -231,7 +238,10 @@ stopLayout->addWidget(pi.get()); exposureIN->setValue(Options::alignExposure()); - connect(exposureIN, static_cast(&QDoubleSpinBox::valueChanged), [&]() { syncSettings();}); + connect(exposureIN, static_cast(&QDoubleSpinBox::valueChanged), [&]() + { + syncSettings(); + }); altStage = ALT_INIT; azStage = AZ_INIT; @@ -246,27 +256,28 @@ #ifdef Q_OS_WIN offlineSolverR->setEnabled(false); offlineSolverR->setToolTip( - i18n("Offline solver is not supported under Windows. Please use either the Online or Remote solvers.")); + i18n("Offline solver is not supported under Windows. Please use either the Online or Remote solvers.")); #endif solverTypeGroup->button(Options::solverType())->setChecked(true); - connect(solverTypeGroup, SIGNAL(buttonClicked(int)), SLOT(setSolverType(int))); + connect(solverTypeGroup, static_cast(&QButtonGroup::buttonClicked), + this, &Align::setSolverType); switch (solverTypeGroup->checkedId()) { - case SOLVER_ONLINE: - onlineParser.reset(new Ekos::OnlineAstrometryParser()); - parser = onlineParser.get(); - break; + case SOLVER_ONLINE: + onlineParser.reset(new Ekos::OnlineAstrometryParser()); + parser = onlineParser.get(); + break; - case SOLVER_OFFLINE: - offlineParser.reset(new OfflineAstrometryParser()); - parser = offlineParser.get(); - break; + case SOLVER_OFFLINE: + offlineParser.reset(new OfflineAstrometryParser()); + parser = offlineParser.get(); + break; - case SOLVER_REMOTE: - remoteParser.reset(new RemoteAstrometryParser()); - parser = remoteParser.get(); - break; + case SOLVER_REMOTE: + remoteParser.reset(new RemoteAstrometryParser()); + parser = remoteParser.get(); + break; } parser->setAlign(this); @@ -296,7 +307,8 @@ connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Align::setBinningIndex); // PAH Connections - connect(this, &Align::PAHEnabled, [&](bool enabled) { + connect(this, &Align::PAHEnabled, [&](bool enabled) + { PAHStartB->setEnabled(enabled); directionLabel->setEnabled(enabled); PAHDirectionCombo->setEnabled(enabled); @@ -384,14 +396,14 @@ solutionTable->setColumnWidth(5, 100); clearAllSolutionsB->setIcon( - QIcon::fromTheme("application-exit")); + QIcon::fromTheme("application-exit")); clearAllSolutionsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); removeSolutionB->setIcon(QIcon::fromTheme("list-remove")); removeSolutionB->setAttribute(Qt::WA_LayoutUsesWidgetRect); exportSolutionsCSV->setIcon( - QIcon::fromTheme("document-save-as")); + QIcon::fromTheme("document-save-as")); exportSolutionsCSV->setAttribute(Qt::WA_LayoutUsesWidgetRect); autoScaleGraphB->setIcon(QIcon::fromTheme("zoom-fit-best")); @@ -406,11 +418,11 @@ mountModel.alignTable->setColumnWidth(3, 30); mountModel.wizardAlignB->setIcon( - QIcon::fromTheme("tools-wizard")); + QIcon::fromTheme("tools-wizard")); mountModel.wizardAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.clearAllAlignB->setIcon( - QIcon::fromTheme("application-exit")); + QIcon::fromTheme("application-exit")); mountModel.clearAllAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.removeAlignB->setIcon(QIcon::fromTheme("list-remove")); @@ -426,19 +438,19 @@ mountModel.alignTable->verticalHeader()->setSectionsMovable(true); mountModel.alignTable->verticalHeader()->setDragEnabled(true); mountModel.alignTable->verticalHeader()->setDragDropMode(QAbstractItemView::InternalMove); - connect(mountModel.alignTable->verticalHeader(), SIGNAL(sectionMoved(int,int,int)), this, - SLOT(moveAlignPoint(int,int,int))); + connect(mountModel.alignTable->verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, + SLOT(moveAlignPoint(int, int, int))); mountModel.loadAlignB->setIcon( - QIcon::fromTheme("document-open")); + QIcon::fromTheme("document-open")); mountModel.loadAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.saveAsAlignB->setIcon( - QIcon::fromTheme("document-save-as")); + QIcon::fromTheme("document-save-as")); mountModel.saveAsAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.saveAlignB->setIcon( - QIcon::fromTheme("document-save")); + QIcon::fromTheme("document-save")); mountModel.saveAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.previewB->setIcon(QIcon::fromTheme("kstars_grid")); @@ -449,11 +461,11 @@ mountModel.sortAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.stopAlignB->setIcon( - QIcon::fromTheme("media-playback-stop")); + QIcon::fromTheme("media-playback-stop")); mountModel.stopAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); mountModel.startAlignB->setIcon( - QIcon::fromTheme("media-playback-start")); + QIcon::fromTheme("media-playback-start")); mountModel.startAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(clearAllSolutionsB, &QPushButton::clicked, this, &Ekos::Align::slotClearAllSolutionPoints); @@ -464,12 +476,12 @@ connect(solutionTable, &QTableWidget::cellClicked, this, &Ekos::Align::selectSolutionTableRow); connect(mountModel.wizardAlignB, &QPushButton::clicked, this, &Ekos::Align::slotWizardAlignmentPoints); - connect(mountModel.alignTypeBox, static_cast(&QComboBox::currentIndexChanged), this, + connect(mountModel.alignTypeBox, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Align::alignTypeChanged); - connect(mountModel.starListBox, static_cast(&QComboBox::currentIndexChanged), this, + connect(mountModel.starListBox, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Align::slotStarSelected); - connect(mountModel.greekStarListBox, static_cast(&QComboBox::currentIndexChanged), this, + connect(mountModel.greekStarListBox, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Align::slotStarSelected); connect(mountModel.loadAlignB, &QPushButton::clicked, this, &Ekos::Align::slotLoadAlignmentPoints); @@ -622,24 +634,24 @@ } const int pointCount = 200; QVector circleRings( - pointCount * (5)); //Have to multiply by the number of rings, Rings at : 25%, 50%, 75%, 125%, 175% + 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; + 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)); + accuracyRadius * ring * 0.25 * qSin(theta)); circleRingPt++; } } @@ -667,7 +679,7 @@ 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. + 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 (alignPlot->size().width() > 0) @@ -743,16 +755,16 @@ { decIncrement = 0; spTest.setAlt( - minAlt); //The goal here is to get the point exactly West at the minAlt so that we can use that DEC + minAlt); //The goal here is to get the point exactly West at the minAlt so that we can use that DEC spTest.setAz(270); spTest.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat()); initDEC = spTest.dec().Degrees(); } else { spTest.setAlt( - minAlt + - 10); //We don't want to be right at the minAlt because there would be only 1 point on the dec circle above the alt. + minAlt + + 10); //We don't want to be right at the minAlt because there would be only 1 point on the dec circle above the alt. spTest.setAz(180); spTest.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat()); initDEC = spTest.dec().Degrees(); @@ -1045,7 +1057,8 @@ boxNames.sort(Qt::CaseInsensitive); boxNames.removeDuplicates(); greekBoxNames.removeDuplicates(); - qSort(greekBoxNames.begin(), greekBoxNames.end(), [](const QString &a, const QString &b) { + qSort(greekBoxNames.begin(), greekBoxNames.end(), [](const QString & a, const QString & b) + { QStringList aParts = a.split(' '); QStringList bParts = b.split(' '); if (aParts.length() < 2 || bParts.length() < 2) @@ -1131,7 +1144,7 @@ void Align::slotLoadAlignmentPoints() { QUrl fileURL = QFileDialog::getOpenFileUrl(&mountModelDialog, i18n("Open Ekos Alignment List"), alignURLPath, - "Ekos AlignmentList (*.eal)"); + "Ekos AlignmentList (*.eal)"); if (fileURL.isEmpty()) return; @@ -1264,10 +1277,10 @@ if (QFile::exists(alignURL.toLocalFile())) { int r = KMessageBox::warningContinueCancel(nullptr, - i18n("A file named \"%1\" already exists. " - "Overwrite it?", - alignURL.fileName()), - i18n("Overwrite File?"), KStandardGuiItem::overwrite()); + i18n("A file named \"%1\" already exists. " + "Overwrite it?", + alignURL.fileName()), + i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } @@ -1420,7 +1433,7 @@ return; QUrl exportFile = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Export Solution Points"), alignURLPath, - "CSV File (*.csv)"); + "CSV File (*.csv)"); if (exportFile.isEmpty()) // if user presses cancel return; if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false) @@ -1431,10 +1444,10 @@ 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()); + i18n("A file named \"%1\" already exists. " + "Overwrite it?", + exportFile.fileName()), + i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } @@ -1656,7 +1669,7 @@ appendLogText(i18n("The Mount Model Tool is Reset.")); mountModel.startAlignB->setIcon( - QIcon::fromTheme("media-playback-start")); + QIcon::fromTheme("media-playback-start")); mountModelRunning = false; currentAlignmentPoint = 0; abort(); @@ -1716,16 +1729,16 @@ } } mountModel.startAlignB->setIcon( - QIcon::fromTheme("media-playback-pause")); + QIcon::fromTheme("media-playback-pause")); mountModelRunning = true; appendLogText(i18n("The Mount Model Tool is Starting.")); startAlignmentPoint(); } } else { mountModel.startAlignB->setIcon( - QIcon::fromTheme("media-playback-start")); + QIcon::fromTheme("media-playback-start")); mountModel.alignTable->setCellWidget(currentAlignmentPoint, 3, new QWidget()); appendLogText(i18n("The Mount Model Tool is Paused.")); abort(); @@ -1787,7 +1800,7 @@ { mountModelRunning = false; mountModel.startAlignB->setIcon( - QIcon::fromTheme("media-playback-start")); + QIcon::fromTheme("media-playback-start")); appendLogText(i18n("The Mount Model Tool is Finished.")); currentAlignmentPoint = 0; } @@ -1831,45 +1844,45 @@ switch (type) { - case SOLVER_ONLINE: - loadSlewB->setEnabled(true); - if (onlineParser.get() != nullptr) - { + case SOLVER_ONLINE: + loadSlewB->setEnabled(true); + if (onlineParser.get() != nullptr) + { + parser = onlineParser.get(); + return; + } + + onlineParser.reset(new Ekos::OnlineAstrometryParser()); parser = onlineParser.get(); - return; - } + break; - onlineParser.reset(new Ekos::OnlineAstrometryParser()); - parser = onlineParser.get(); - break; + case SOLVER_OFFLINE: + loadSlewB->setEnabled(true); + if (offlineParser.get() != nullptr) + { + parser = offlineParser.get(); + return; + } - case SOLVER_OFFLINE: - loadSlewB->setEnabled(true); - if (offlineParser.get() != nullptr) - { + offlineParser.reset(new Ekos::OfflineAstrometryParser()); parser = offlineParser.get(); - return; - } + break; - offlineParser.reset(new Ekos::OfflineAstrometryParser()); - parser = offlineParser.get(); - break; + case SOLVER_REMOTE: + loadSlewB->setEnabled(true); + if (remoteParser.get() != nullptr && remoteParserDevice != nullptr) + { + parser = remoteParser.get(); + (dynamic_cast(parser))->setAstrometryDevice(remoteParserDevice); + return; + } - case SOLVER_REMOTE: - loadSlewB->setEnabled(true); - if (remoteParser.get() != nullptr && remoteParserDevice != nullptr) - { + remoteParser.reset(new Ekos::RemoteAstrometryParser()); parser = remoteParser.get(); (dynamic_cast(parser))->setAstrometryDevice(remoteParserDevice); - return; - } - - remoteParser.reset(new Ekos::RemoteAstrometryParser()); - parser = remoteParser.get(); - (dynamic_cast(parser))->setAstrometryDevice(remoteParserDevice); - if (currentCCD) - (dynamic_cast(parser))->setCCD(currentCCD->getDeviceName()); - break; + if (currentCCD) + (dynamic_cast(parser))->setCCD(currentCCD->getDeviceName()); + break; } parser->setAlign(this); @@ -1882,7 +1895,7 @@ parser->disconnect(); } -bool Align::setCamera(const QString & device) +bool Align::setCamera(const QString &device) { for (int i = 0; i < CCDCaptureCombo->count(); i++) if (device == CCDCaptureCombo->itemText(i)) @@ -1963,7 +1976,8 @@ currentTelescope->disconnect(this); connect(currentTelescope, &ISD::GDInterface::numberUpdated, this, &Ekos::Align::processNumber, Qt::UniqueConnection); - connect(currentTelescope, &ISD::GDInterface::Disconnected, this, [this]() { + connect(currentTelescope, &ISD::GDInterface::Disconnected, this, [this]() + { m_isRateSynced = false; }); @@ -1982,7 +1996,7 @@ m_isRateSynced = !currentTelescope->slewRates().empty(); } - syncTelescopeInfo(); + syncTelescopeInfo(); } void Align::setDome(ISD::GDInterface *newDome) @@ -2046,19 +2060,19 @@ if (ccd_hor_pixel != -1 && ccd_ver_pixel != -1 && focal_length != -1 && aperture != -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); + 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); + 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); calculateFOV(); @@ -2149,8 +2163,8 @@ // to whatever value the user selected. if (Options::solverBinningIndex() == 4 && binningCombo->count() <= 4) { - binningCombo->setCurrentIndex(binningCombo->count()-1); - Options::setSolverBinningIndex(binningCombo->count()-1); + binningCombo->setCurrentIndex(binningCombo->count() - 1); + Options::setSolverBinningIndex(binningCombo->count() - 1); } else binningCombo->setCurrentIndex(Options::solverBinningIndex()); @@ -2494,8 +2508,8 @@ if (focal_length == -1 || aperture == -1) { KMessageBox::error( - nullptr, - i18n("Telescope aperture and focal length are missing. Please check your driver settings and try again.")); + nullptr, + i18n("Telescope aperture and focal length are missing. Please check your driver settings and try again.")); return false; } @@ -2683,7 +2697,7 @@ double maxrad = 1.0; SkyObject *so = - KStarsData::Instance()->skyComposite()->objectNearest(new SkyPoint(dms(ra * 15), dms(dec)), maxrad); + KStarsData::Instance()->skyComposite()->objectNearest(new SkyPoint(dms(ra * 15), dms(dec)), maxrad); QString name; if (so) { @@ -2739,7 +2753,7 @@ if (blobType == ISD::CCD::BLOB_FITS) { ISD::CCDChip *targetChip = - currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (alignDarkFrameCheck->isChecked()) { @@ -2759,7 +2773,7 @@ else { bool rc = DarkLibrary::Instance()->captureAndSubtract(targetChip, alignView, exposureIN->value(), - offsetX, offsetY); + offsetX, offsetY); alignDarkFrameCheck->setChecked(rc); } @@ -2787,7 +2801,7 @@ if (solverTypeGroup->checkedId() == SOLVER_ONLINE && Options::astrometryUseJPEG()) { ISD::CCDChip *targetChip = - currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip) { QString jpegFile = blobFileName + ".jpg"; @@ -2846,10 +2860,10 @@ i18n("Mount must be pointing close to the target location and current field of view must " "match the image's field of view.")); int rc = KMessageBox::questionYesNoCancel(nullptr, - i18n("No metadata is available in this image. Do you want to use the " - "blind solver or the existing solver settings?"), - i18n("Astrometry solver"), blindItem, existingItem, - KStandardGuiItem::cancel(), "blind_solver_or_existing_solver_option"); + i18n("No metadata is available in this image. Do you want to use the " + "blind solver or the existing solver settings?"), + i18n("Astrometry solver"), blindItem, existingItem, + KStandardGuiItem::cancel(), "blind_solver_or_existing_solver_option"); if (rc == KMessageBox::Yes) { QVariantMap optionsMap; @@ -3078,11 +3092,11 @@ if (m_wcsSynced == false) { appendLogText( - i18n("WCS information updated. Images captured from this point forward shall have valid WCS.")); + i18n("WCS information updated. Images captured from this point forward shall have valid WCS.")); // Just send telescope info in case the CCD driver did not pick up before. INumberVectorProperty *telescopeInfo = - currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); + currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); if (telescopeInfo) clientManager->sendNewNumber(telescopeInfo); @@ -3165,7 +3179,8 @@ } emit newSolverResults(orientation, ra, dec, pixscale); - QJsonObject solution = { + QJsonObject solution = + { {"ra", SolverRAOut->text()}, {"de", SolverDecOut->text()}, {"dRA", dRAOut->text()}, @@ -3178,77 +3193,77 @@ switch (currentGotoMode) { - case GOTO_SYNC: - executeGOTO(); + case GOTO_SYNC: + executeGOTO(); - if (loadSlewState == IPS_IDLE) - { - statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg")); - solutionTable->setItem(currentRow, 3, statusReport.release()); - } + if (loadSlewState == IPS_IDLE) + { + statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg")); + solutionTable->setItem(currentRow, 3, statusReport.release()); + } - return; + return; - case GOTO_SLEW: - if (loadSlewState == IPS_BUSY || targetDiff > static_cast(accuracySpin->value())) - { - if (loadSlewState == IPS_IDLE && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS) + case GOTO_SLEW: + if (loadSlewState == IPS_BUSY || targetDiff > static_cast(accuracySpin->value())) { - appendLogText(i18n("Maximum number of iterations reached. Solver failed.")); + if (loadSlewState == IPS_IDLE && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS) + { + appendLogText(i18n("Maximum number of iterations reached. Solver failed.")); + + if (loadSlewState == IPS_IDLE) + { + statusReport->setIcon(QIcon(":/icons/AlignFailure.svg")); + solutionTable->setItem(currentRow, 3, statusReport.release()); + } + + solverFailed(); + if (mountModelRunning) + finishAlignmentPoint(false); + return; + } + + targetAccuracyNotMet = true; if (loadSlewState == IPS_IDLE) { - statusReport->setIcon(QIcon(":/icons/AlignFailure.svg")); + statusReport->setIcon(QIcon(":/icons/AlignWarning.svg")); solutionTable->setItem(currentRow, 3, statusReport.release()); } - solverFailed(); - if (mountModelRunning) - finishAlignmentPoint(false); + executeGOTO(); return; } - targetAccuracyNotMet = true; - if (loadSlewState == IPS_IDLE) { - statusReport->setIcon(QIcon(":/icons/AlignWarning.svg")); + statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg")); solutionTable->setItem(currentRow, 3, statusReport.release()); } - executeGOTO(); - return; - } - - if (loadSlewState == IPS_IDLE) - { - statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg")); - solutionTable->setItem(currentRow, 3, statusReport.release()); - } - - appendLogText(i18n("Target is within acceptable range. Astrometric solver is successful.")); + appendLogText(i18n("Target is within acceptable range. Astrometric solver is successful.")); - if (mountModelRunning) - { - finishAlignmentPoint(true); if (mountModelRunning) - return; - } - break; + { + finishAlignmentPoint(true); + if (mountModelRunning) + return; + } + break; - case GOTO_NOTHING: - if (loadSlewState == IPS_IDLE) - { - statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg")); - solutionTable->setItem(currentRow, 3, statusReport.release()); - } - if (mountModelRunning) - { - finishAlignmentPoint(true); + case GOTO_NOTHING: + if (loadSlewState == IPS_IDLE) + { + statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg")); + solutionTable->setItem(currentRow, 3, statusReport.release()); + } if (mountModelRunning) - return; - } - break; + { + finishAlignmentPoint(true); + if (mountModelRunning) + return; + } + break; } KSNotification::event(QLatin1String("AlignSuccessful"), i18n("Astrometry alignment completed successfully")); @@ -3269,7 +3284,7 @@ void Align::solverFailed() { - KSNotification::event(QLatin1String("AlignFailed"), i18n("Astrometry alignment failed with errors"),KSNotification::EVENT_ALERT); + KSNotification::event(QLatin1String("AlignFailed"), i18n("Astrometry alignment failed with errors"), KSNotification::EVENT_ALERT); pi->stopAnimation(); stopB->setEnabled(false); @@ -3449,201 +3464,203 @@ switch (nvp->s) { - case IPS_OK: - { - // Update the boxes as the mount just finished slewing - if (isSlewDirty && Options::astrometryAutoUpdatePosition()) + case IPS_OK: { - opsAstrometry->estRA->setText(ra_dms); - opsAstrometry->estDec->setText(dec_dms); - - Options::setAstrometryPositionRA(nvp->np[0].value * 15); - Options::setAstrometryPositionDE(nvp->np[1].value); + // Update the boxes as the mount just finished slewing + if (isSlewDirty && Options::astrometryAutoUpdatePosition()) + { + opsAstrometry->estRA->setText(ra_dms); + opsAstrometry->estDec->setText(dec_dms); - generateArgs(); - } + Options::setAstrometryPositionRA(nvp->np[0].value * 15); + Options::setAstrometryPositionDE(nvp->np[1].value); - // If dome is syncing, wait until it stops - if (currentDome && currentDome->isMoving()) - { - domeReady = false; - return; - } + generateArgs(); + } - if (isSlewDirty && pahStage == PAH_FIND_CP) - { - isSlewDirty = false; - appendLogText(i18n("Mount completed slewing near celestial pole. Capture again to verify.")); - setSolverAction(GOTO_NOTHING); - pahStage = PAH_FIRST_CAPTURE; - emit newPAHStage(pahStage); - return; - } + // If dome is syncing, wait until it stops + if (currentDome && currentDome->isMoving()) + { + domeReady = false; + return; + } - // if (isSlewDirty && pahStage == PAH_FIRST_ROTATE) - // { - // isSlewDirty = false; + if (isSlewDirty && pahStage == PAH_FIND_CP) + { + isSlewDirty = false; + appendLogText(i18n("Mount completed slewing near celestial pole. Capture again to verify.")); + setSolverAction(GOTO_NOTHING); + pahStage = PAH_FIRST_CAPTURE; + emit newPAHStage(pahStage); + return; + } - // appendLogText(i18n("Mount first rotation is complete.")); + // if (isSlewDirty && pahStage == PAH_FIRST_ROTATE) + // { + // isSlewDirty = false; - // pahStage = PAH_SECOND_CAPTURE; - // emit newPAHStage(pahStage); + // appendLogText(i18n("Mount first rotation is complete.")); + // pahStage = PAH_SECOND_CAPTURE; + // emit newPAHStage(pahStage); - // PAHWidgets->setCurrentWidget(PAHSecondCapturePage); - // emit newPAHMessage(secondCaptureText->text()); - // if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) - // appendLogText(i18n("Settling...")); - // QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); - // return; - // } - // else if (isSlewDirty && pahStage == PAH_SECOND_ROTATE) - // { - // isSlewDirty = false; + // PAHWidgets->setCurrentWidget(PAHSecondCapturePage); + // emit newPAHMessage(secondCaptureText->text()); - // appendLogText(i18n("Mount second rotation is complete.")); + // if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) + // appendLogText(i18n("Settling...")); + // QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); + // return; + // } + // else if (isSlewDirty && pahStage == PAH_SECOND_ROTATE) + // { + // isSlewDirty = false; - // pahStage = PAH_THIRD_CAPTURE; - // emit newPAHStage(pahStage); + // appendLogText(i18n("Mount second rotation is complete.")); + // pahStage = PAH_THIRD_CAPTURE; + // emit newPAHStage(pahStage); - // PAHWidgets->setCurrentWidget(PAHThirdCapturePage); - // emit newPAHMessage(thirdCaptureText->text()); - // if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) - // appendLogText(i18n("Settling...")); - // QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); - // return; - // } + // PAHWidgets->setCurrentWidget(PAHThirdCapturePage); + // emit newPAHMessage(thirdCaptureText->text()); - switch (state) - { - case ALIGN_PROGRESS: - break; + // if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) + // appendLogText(i18n("Settling...")); + // QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); + // return; + // } - case ALIGN_SYNCING: - { - isSlewDirty = false; - if (currentGotoMode == GOTO_SLEW) + switch (state) { - Slew(); - return; - } - else - { - appendLogText(i18n("Mount is synced to solution coordinates. Astrometric solver is successful.")); - KSNotification::event(QLatin1String("AlignSuccessful"), - i18n("Astrometry alignment completed successfully")); - state = ALIGN_COMPLETE; - emit newStatus(state); - solverIterations = 0; - - if (mountModelRunning) - finishAlignmentPoint(true); - } - } - break; + case ALIGN_PROGRESS: + break; - case ALIGN_SLEWING: - if (isSlewDirty == false) + case ALIGN_SYNCING: + { + isSlewDirty = false; + if (currentGotoMode == GOTO_SLEW) + { + Slew(); + return; + } + else + { + appendLogText(i18n("Mount is synced to solution coordinates. Astrometric solver is successful.")); + KSNotification::event(QLatin1String("AlignSuccessful"), + i18n("Astrometry alignment completed successfully")); + state = ALIGN_COMPLETE; + emit newStatus(state); + solverIterations = 0; + + if (mountModelRunning) + finishAlignmentPoint(true); + } + } break; - isSlewDirty = false; - if (loadSlewState == IPS_BUSY) - { - loadSlewState = IPS_IDLE; - - qCDebug(KSTARS_EKOS_ALIGN) << "loadSlewState is IDLE."; - - state = ALIGN_PROGRESS; - emit newStatus(state); - - if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) - appendLogText(i18n("Settling...")); - QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); - return; - } - else if (differentialSlewingActivated) - { - appendLogText(i18n("Differential slewing complete. Astrometric solver is successful.")); - KSNotification::event(QLatin1String("AlignSuccessful"), i18n("Astrometry alignment completed successfully")); - state = ALIGN_COMPLETE; - emit newStatus(state); - solverIterations = 0; - - if (mountModelRunning) - finishAlignmentPoint(true); - } - else if (currentGotoMode == GOTO_SLEW || mountModelRunning) - { - if (targetAccuracyNotMet) - appendLogText(i18n("Slew complete. Target accuracy is not met, running solver again...")); - else - appendLogText(i18n("Slew complete. Solving Alignment Point. . .")); - - targetAccuracyNotMet = false; - - state = ALIGN_PROGRESS; - emit newStatus(state); - - if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) - appendLogText(i18n("Settling...")); - QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); - return; + case ALIGN_SLEWING: + if (isSlewDirty == false) + break; + + isSlewDirty = false; + if (loadSlewState == IPS_BUSY) + { + loadSlewState = IPS_IDLE; + + qCDebug(KSTARS_EKOS_ALIGN) << "loadSlewState is IDLE."; + + state = ALIGN_PROGRESS; + emit newStatus(state); + + if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) + appendLogText(i18n("Settling...")); + QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); + return; + } + else if (differentialSlewingActivated) + { + appendLogText(i18n("Differential slewing complete. Astrometric solver is successful.")); + KSNotification::event(QLatin1String("AlignSuccessful"), i18n("Astrometry alignment completed successfully")); + state = ALIGN_COMPLETE; + emit newStatus(state); + solverIterations = 0; + + if (mountModelRunning) + finishAlignmentPoint(true); + } + else if (currentGotoMode == GOTO_SLEW || mountModelRunning) + { + if (targetAccuracyNotMet) + appendLogText(i18n("Slew complete. Target accuracy is not met, running solver again...")); + else + appendLogText(i18n("Slew complete. Solving Alignment Point. . .")); + + targetAccuracyNotMet = false; + + state = ALIGN_PROGRESS; + emit newStatus(state); + + if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) + appendLogText(i18n("Settling...")); + QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); + return; + } + break; + + default: + { + isSlewDirty = false; + } + break; } - break; - - default: - { - isSlewDirty = false; } - break; - } - } break; - case IPS_BUSY: - { - isSlewDirty = true; - } + case IPS_BUSY: + { + isSlewDirty = true; + } break; - case IPS_ALERT: - { - if (state == ALIGN_SYNCING || state == ALIGN_SLEWING) + case IPS_ALERT: { - if (state == ALIGN_SYNCING) - appendLogText(i18n("Syncing failed.")); - else - appendLogText(i18n("Slewing failed.")); - - if (++m_SlewErrorCounter == 3) - { - abort(); - return; - } - else + if (state == ALIGN_SYNCING || state == ALIGN_SLEWING) { - if (currentGotoMode == GOTO_SLEW) - Slew(); + if (state == ALIGN_SYNCING) + appendLogText(i18n("Syncing failed.")); else - Sync(); + appendLogText(i18n("Slewing failed.")); + + if (++m_SlewErrorCounter == 3) + { + abort(); + return; + } + else + { + if (currentGotoMode == GOTO_SLEW) + Slew(); + else + Sync(); + } } - } - return; - } + return; + } - default: - break; + default: + break; } if (pahStage == PAH_FIRST_ROTATE) { // only wait for telescope to slew to new position if manual slewing is switched off - if(!PAHManual->isChecked()){ + if(!PAHManual->isChecked()) + { double deltaAngle = fabs(telescopeCoord.ra().deltaAngle(targetPAH.ra()).Degrees()); + qCDebug(KSTARS_EKOS_ALIGN) << "First mount rotation remainging degrees:" << deltaAngle; if (deltaAngle <= PAH_ROTATION_THRESHOLD) { currentTelescope->StopWE(); @@ -3660,20 +3677,22 @@ QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); } // If for some reason we didn't stop, let's stop if we get too far - else if (deltaAngle > PAHRotationSpin->value()*1.25) + else if (deltaAngle > PAHRotationSpin->value() * 1.25) { currentTelescope->Abort(); appendLogText(i18n("Mount aborted. Please restart the process and reduce the speed.")); stopPAHProcess(); } return; - } // endif not manual slew + } // endif not manual slew } else if (pahStage == PAH_SECOND_ROTATE) { // only wait for telescope to slew to new position if manual slewing is switched off - if(!PAHManual->isChecked()){ + if(!PAHManual->isChecked()) + { double deltaAngle = fabs(telescopeCoord.ra().deltaAngle(targetPAH.ra()).Degrees()); + qCDebug(KSTARS_EKOS_ALIGN) << "Second mount rotation remainging degrees:" << deltaAngle; if (deltaAngle <= PAH_ROTATION_THRESHOLD) { currentTelescope->StopWE(); @@ -3691,7 +3710,7 @@ QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); } // If for some reason we didn't stop, let's stop if we get too far - else if (deltaAngle > PAHRotationSpin->value()*1.25) + else if (deltaAngle > PAHRotationSpin->value() * 1.25) { currentTelescope->Abort(); appendLogText(i18n("Mount aborted. Please restart the process and reduce the speed.")); @@ -3703,57 +3722,57 @@ switch (azStage) { - case AZ_SYNCING: - if (currentTelescope->isSlewing()) - azStage = AZ_SLEWING; - break; + case AZ_SYNCING: + if (currentTelescope->isSlewing()) + azStage = AZ_SLEWING; + break; - case AZ_SLEWING: - if (currentTelescope->isSlewing() == false) - { - azStage = AZ_SECOND_TARGET; - measureAzError(); - } - break; + case AZ_SLEWING: + if (currentTelescope->isSlewing() == false) + { + azStage = AZ_SECOND_TARGET; + measureAzError(); + } + break; - case AZ_CORRECTING: - if (currentTelescope->isSlewing() == false) - { - appendLogText(i18n( - "Slew complete. Please adjust azimuth knob until the target is in the center of the view.")); - azStage = AZ_INIT; - } - break; + case AZ_CORRECTING: + if (currentTelescope->isSlewing() == false) + { + appendLogText(i18n( + "Slew complete. Please adjust azimuth knob until the target is in the center of the view.")); + azStage = AZ_INIT; + } + break; - default: - break; + default: + break; } switch (altStage) { - case ALT_SYNCING: - if (currentTelescope->isSlewing()) - altStage = ALT_SLEWING; - break; + case ALT_SYNCING: + if (currentTelescope->isSlewing()) + altStage = ALT_SLEWING; + break; - case ALT_SLEWING: - if (currentTelescope->isSlewing() == false) - { - altStage = ALT_SECOND_TARGET; - measureAltError(); - } - break; + case ALT_SLEWING: + if (currentTelescope->isSlewing() == false) + { + altStage = ALT_SECOND_TARGET; + measureAltError(); + } + break; - case ALT_CORRECTING: - if (currentTelescope->isSlewing() == false) - { - appendLogText(i18n( - "Slew complete. Please adjust altitude knob until the target is in the center of the view.")); - altStage = ALT_INIT; - } - break; - default: - break; + case ALT_CORRECTING: + if (currentTelescope->isSlewing() == false) + { + appendLogText(i18n( + "Slew complete. Please adjust altitude knob until the target is in the center of the view.")); + altStage = ALT_INIT; + } + break; + default: + break; } } else if (!strcmp(nvp->name, "ABS_ROTATOR_ANGLE")) @@ -3764,7 +3783,7 @@ currentRotatorPA -= 360; if (currentRotatorPA < -180) currentRotatorPA += 360; - if (std::isnan(loadSlewTargetPA) == false && fabs(currentRotatorPA - loadSlewTargetPA)*60 <= Options::astrometryRotatorThreshold()) + if (std::isnan(loadSlewTargetPA) == false && fabs(currentRotatorPA - loadSlewTargetPA) * 60 <= Options::astrometryRotatorThreshold()) { appendLogText(i18n("Rotator reached target position angle.")); targetAccuracyNotMet = true; @@ -3803,7 +3822,7 @@ { emit newStatus(state); appendLogText( - i18n("Syncing to RA (%1) DEC (%2)", alignCoord.ra().toHMSString(), alignCoord.dec().toDMSString())); + i18n("Syncing to RA (%1) DEC (%2)", alignCoord.ra().toHMSString(), alignCoord.dec().toDMSString())); } else { @@ -3843,8 +3862,8 @@ dms raDiff = alignCoord.ra().deltaAngle(targetCoord.ra()); dms deDiff = alignCoord.dec().deltaAngle(targetCoord.dec()); - targetCoord.setRA(targetCoord.ra()-raDiff); - targetCoord.setDec(targetCoord.dec()-deDiff); + targetCoord.setRA(targetCoord.ra() - raDiff); + targetCoord.setDec(targetCoord.dec() - deDiff); differentialSlewingActivated = true; @@ -3867,24 +3886,24 @@ switch (azStage) { - case AZ_FIRST_TARGET: - case AZ_FINISHED: - measureAzError(); - break; + case AZ_FIRST_TARGET: + case AZ_FINISHED: + measureAzError(); + break; - default: - break; + default: + break; } switch (altStage) { - case ALT_FIRST_TARGET: - case ALT_FINISHED: - measureAltError(); - break; + case ALT_FIRST_TARGET: + case ALT_FINISHED: + measureAltError(); + break; - default: - break; + default: + break; } } @@ -3894,8 +3913,8 @@ if (pahStage != PAH_IDLE && (KMessageBox::warningContinueCancel(KStars::Instance(), - i18n("Polar Alignment Helper is still active. Do you want to continue " - "using legacy polar alignment tool?")) != KMessageBox::Continue)) + i18n("Polar Alignment Helper is still active. Do you want to continue " + "using legacy polar alignment tool?")) != KMessageBox::Continue)) return; pahStage = PAH_IDLE; @@ -3905,88 +3924,88 @@ switch (azStage) { - case AZ_INIT: + case AZ_INIT: - // Display message box confirming user point scope near meridian and south + // Display message box confirming user point scope near meridian and south - if (KMessageBox::warningContinueCancel( - nullptr, - hemisphere == NORTH_HEMISPHERE ? - i18n("Point the telescope at the southern meridian. Press Continue when ready.") : - i18n("Point the telescope at the northern meridian. Press Continue when ready."), - i18n("Polar Alignment Measurement"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), - "ekos_measure_az_error") != KMessageBox::Continue) - return; + if (KMessageBox::warningContinueCancel( + nullptr, + hemisphere == NORTH_HEMISPHERE ? + i18n("Point the telescope at the southern meridian. Press Continue when ready.") : + i18n("Point the telescope at the northern meridian. Press Continue when ready."), + i18n("Polar Alignment Measurement"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), + "ekos_measure_az_error") != KMessageBox::Continue) + return; - appendLogText(i18n("Solving first frame near the meridian.")); - azStage = AZ_FIRST_TARGET; - captureAndSolve(); - break; + appendLogText(i18n("Solving first frame near the meridian.")); + azStage = AZ_FIRST_TARGET; + captureAndSolve(); + break; - case AZ_FIRST_TARGET: - // start solving there, find RA/DEC - initRA = alignCoord.ra().Degrees(); - initDEC = alignCoord.dec().Degrees(); - initAz = alignCoord.az().Degrees(); + case AZ_FIRST_TARGET: + // start solving there, find RA/DEC + initRA = alignCoord.ra().Degrees(); + initDEC = alignCoord.dec().Degrees(); + initAz = alignCoord.az().Degrees(); - qCDebug(KSTARS_EKOS_ALIGN) << "Polar initRA " << alignCoord.ra().toHMSString() << " initDEC " - << alignCoord.dec().toDMSString() << " initlAz " << alignCoord.az().toDMSString() - << " initAlt " << alignCoord.alt().toDMSString(); + qCDebug(KSTARS_EKOS_ALIGN) << "Polar initRA " << alignCoord.ra().toHMSString() << " initDEC " + << alignCoord.dec().toDMSString() << " initlAz " << alignCoord.az().toDMSString() + << " initAlt " << alignCoord.alt().toDMSString(); - // Now move 30 arcminutes in RA - if (canSync) - { - azStage = AZ_SYNCING; - currentTelescope->Sync(initRA / 15.0, initDEC); - currentTelescope->Slew((initRA - RAMotion) / 15.0, initDEC); - } - // If telescope doesn't sync, we slew relative to its current coordinates - else - { - azStage = AZ_SLEWING; - currentTelescope->Slew(telescopeCoord.ra().Hours() - RAMotion / 15.0, telescopeCoord.dec().Degrees()); - } + // Now move 30 arcminutes in RA + if (canSync) + { + azStage = AZ_SYNCING; + currentTelescope->Sync(initRA / 15.0, initDEC); + currentTelescope->Slew((initRA - RAMotion) / 15.0, initDEC); + } + // If telescope doesn't sync, we slew relative to its current coordinates + else + { + azStage = AZ_SLEWING; + currentTelescope->Slew(telescopeCoord.ra().Hours() - RAMotion / 15.0, telescopeCoord.dec().Degrees()); + } - appendLogText(i18n("Slewing 30 arcminutes in RA...")); - break; + appendLogText(i18n("Slewing 30 arcminutes in RA...")); + break; - case AZ_SECOND_TARGET: - // We reached second target now - // Let now solver for RA/DEC - appendLogText(i18n("Solving second frame near the meridian.")); - azStage = AZ_FINISHED; - captureAndSolve(); - break; - - case AZ_FINISHED: - // Measure deviation in DEC - // Call function to report error - // set stage to AZ_FIRST_TARGET again - appendLogText(i18n("Calculating azimuth alignment error...")); - finalRA = alignCoord.ra().Degrees(); - finalDEC = alignCoord.dec().Degrees(); - - qCDebug(KSTARS_EKOS_ALIGN) << "Polar finalRA " << alignCoord.ra().toHMSString() << " finalDEC " - << alignCoord.dec().toDMSString() << " finalAz " << alignCoord.az().toDMSString() - << " finalAlt " << alignCoord.alt().toDMSString(); - - // Slew back to original position - if (canSync) - currentTelescope->Slew(initRA / 15.0, initDEC); - else - { - currentTelescope->Slew(telescopeCoord.ra().Hours() + RAMotion / 15.0, telescopeCoord.dec().Degrees()); - } + case AZ_SECOND_TARGET: + // We reached second target now + // Let now solver for RA/DEC + appendLogText(i18n("Solving second frame near the meridian.")); + azStage = AZ_FINISHED; + captureAndSolve(); + break; - appendLogText(i18n("Slewing back to original position...")); + case AZ_FINISHED: + // Measure deviation in DEC + // Call function to report error + // set stage to AZ_FIRST_TARGET again + appendLogText(i18n("Calculating azimuth alignment error...")); + finalRA = alignCoord.ra().Degrees(); + finalDEC = alignCoord.dec().Degrees(); + + qCDebug(KSTARS_EKOS_ALIGN) << "Polar finalRA " << alignCoord.ra().toHMSString() << " finalDEC " + << alignCoord.dec().toDMSString() << " finalAz " << alignCoord.az().toDMSString() + << " finalAlt " << alignCoord.alt().toDMSString(); + + // Slew back to original position + if (canSync) + currentTelescope->Slew(initRA / 15.0, initDEC); + else + { + currentTelescope->Slew(telescopeCoord.ra().Hours() + RAMotion / 15.0, telescopeCoord.dec().Degrees()); + } - calculatePolarError(initRA, initDEC, finalRA, finalDEC, initAz); + appendLogText(i18n("Slewing back to original position...")); - azStage = AZ_INIT; - break; + calculatePolarError(initRA, initDEC, finalRA, finalDEC, initAz); - default: - break; + azStage = AZ_INIT; + break; + + default: + break; } } @@ -3996,8 +4015,8 @@ if (pahStage != PAH_IDLE && (KMessageBox::warningContinueCancel(KStars::Instance(), - i18n("Polar Alignment Helper is still active. Do you want to continue " - "using legacy polar alignment tool?")) != KMessageBox::Continue)) + i18n("Polar Alignment Helper is still active. Do you want to continue " + "using legacy polar alignment tool?")) != KMessageBox::Continue)) return; pahStage = PAH_IDLE; @@ -4007,87 +4026,92 @@ switch (altStage) { - case ALT_INIT: + case ALT_INIT: - // Display message box confirming user point scope near meridian and south + // Display message box confirming user point scope near meridian and south - if (KMessageBox::warningContinueCancel(nullptr, - i18n("Point the telescope to the eastern or western horizon with a " - "minimum altitude of 20 degrees. Press continue when ready."), - i18n("Polar Alignment Measurement"), KStandardGuiItem::cont(), - KStandardGuiItem::cancel(), - "ekos_measure_alt_error") != KMessageBox::Continue) - return; + if (KMessageBox::warningContinueCancel(nullptr, + i18n("Point the telescope to the eastern or western horizon with a " + "minimum altitude of 20 degrees. Press continue when ready."), + i18n("Polar Alignment Measurement"), KStandardGuiItem::cont(), + KStandardGuiItem::cancel(), + "ekos_measure_alt_error") != KMessageBox::Continue) + return; - appendLogText(i18n("Solving first frame.")); - altStage = ALT_FIRST_TARGET; - captureAndSolve(); - break; + appendLogText(i18n("Solving first frame.")); + altStage = ALT_FIRST_TARGET; + if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) + appendLogText(i18n("Settling...")); + QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); + break; - case ALT_FIRST_TARGET: - // start solving there, find RA/DEC - initRA = alignCoord.ra().Degrees(); - initDEC = alignCoord.dec().Degrees(); - initAz = alignCoord.az().Degrees(); + case ALT_FIRST_TARGET: + // start solving there, find RA/DEC + initRA = alignCoord.ra().Degrees(); + initDEC = alignCoord.dec().Degrees(); + initAz = alignCoord.az().Degrees(); - qCDebug(KSTARS_EKOS_ALIGN) << "Polar initRA " << alignCoord.ra().toHMSString() << " initDEC " - << alignCoord.dec().toDMSString() << " initlAz " << alignCoord.az().toDMSString() - << " initAlt " << alignCoord.alt().toDMSString(); + qCDebug(KSTARS_EKOS_ALIGN) << "Polar initRA " << alignCoord.ra().toHMSString() << " initDEC " + << alignCoord.dec().toDMSString() << " initlAz " << alignCoord.az().toDMSString() + << " initAlt " << alignCoord.alt().toDMSString(); - // Now move 30 arcminutes in RA - if (canSync) - { - altStage = ALT_SYNCING; - currentTelescope->Sync(initRA / 15.0, initDEC); - currentTelescope->Slew((initRA - RAMotion) / 15.0, initDEC); - } - // If telescope doesn't sync, we slew relative to its current coordinates - else - { - altStage = ALT_SLEWING; - currentTelescope->Slew(telescopeCoord.ra().Hours() - RAMotion / 15.0, telescopeCoord.dec().Degrees()); - } + // Now move 30 arcminutes in RA + if (canSync) + { + altStage = ALT_SYNCING; + currentTelescope->Sync(initRA / 15.0, initDEC); + currentTelescope->Slew((initRA - RAMotion) / 15.0, initDEC); + } + // If telescope doesn't sync, we slew relative to its current coordinates + else + { + altStage = ALT_SLEWING; + currentTelescope->Slew(telescopeCoord.ra().Hours() - RAMotion / 15.0, telescopeCoord.dec().Degrees()); + } - appendLogText(i18n("Slewing 30 arcminutes in RA...")); - break; + appendLogText(i18n("Slewing 30 arcminutes in RA...")); + break; - case ALT_SECOND_TARGET: - // We reached second target now - // Let now solver for RA/DEC - appendLogText(i18n("Solving second frame.")); - altStage = ALT_FINISHED; - captureAndSolve(); - break; - - case ALT_FINISHED: - // Measure deviation in DEC - // Call function to report error - appendLogText(i18n("Calculating altitude alignment error...")); - finalRA = alignCoord.ra().Degrees(); - finalDEC = alignCoord.dec().Degrees(); - - qCDebug(KSTARS_EKOS_ALIGN) << "Polar finalRA " << alignCoord.ra().toHMSString() << " finalDEC " - << alignCoord.dec().toDMSString() << " finalAz " << alignCoord.az().toDMSString() - << " finalAlt " << alignCoord.alt().toDMSString(); - - // Slew back to original position - if (canSync) - currentTelescope->Slew(initRA / 15.0, initDEC); - // If telescope doesn't sync, we slew relative to its current coordinates - else - { - currentTelescope->Slew(telescopeCoord.ra().Hours() + RAMotion / 15.0, telescopeCoord.dec().Degrees()); - } + case ALT_SECOND_TARGET: + // We reached second target now + // Let now solver for RA/DEC + appendLogText(i18n("Solving second frame.")); + altStage = ALT_FINISHED; - appendLogText(i18n("Slewing back to original position...")); + if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) + appendLogText(i18n("Settling...")); + QTimer::singleShot(delaySpin->value(), this, &Ekos::Align::captureAndSolve); + break; + + case ALT_FINISHED: + // Measure deviation in DEC + // Call function to report error + appendLogText(i18n("Calculating altitude alignment error...")); + finalRA = alignCoord.ra().Degrees(); + finalDEC = alignCoord.dec().Degrees(); + + qCDebug(KSTARS_EKOS_ALIGN) << "Polar finalRA " << alignCoord.ra().toHMSString() << " finalDEC " + << alignCoord.dec().toDMSString() << " finalAz " << alignCoord.az().toDMSString() + << " finalAlt " << alignCoord.alt().toDMSString(); + + // Slew back to original position + if (canSync) + currentTelescope->Slew(initRA / 15.0, initDEC); + // If telescope doesn't sync, we slew relative to its current coordinates + else + { + currentTelescope->Slew(telescopeCoord.ra().Hours() + RAMotion / 15.0, telescopeCoord.dec().Degrees()); + } + + appendLogText(i18n("Slewing back to original position...")); - calculatePolarError(initRA, initDEC, finalRA, finalDEC, initAz); + calculatePolarError(initRA, initDEC, finalRA, finalDEC, initAz); - altStage = ALT_INIT; - break; + altStage = ALT_INIT; + break; - default: - break; + default: + break; } } @@ -4115,79 +4139,79 @@ switch (hemisphere) { - // Northern hemisphere - case NORTH_HEMISPHERE: - if (azStage == AZ_FINISHED) - { - if (decDeviation > 0) - deviationDirection = ki18n("%1 too far east"); - else - deviationDirection = ki18n("%1 too far west"); - } - else if (altStage == ALT_FINISHED) - { - switch (horizon) + // Northern hemisphere + case NORTH_HEMISPHERE: + if (azStage == AZ_FINISHED) { - // East - case 0: - if (decDeviation > 0) - deviationDirection = ki18n("%1 too far high"); - else - deviationDirection = ki18n("%1 too far low"); - - break; - - // West - case 1: if (decDeviation > 0) - deviationDirection = ki18n("%1 too far low"); + deviationDirection = ki18n("%1 too far east"); else - deviationDirection = ki18n("%1 too far high"); - break; - - default: - break; + deviationDirection = ki18n("%1 too far west"); } - } - break; + else if (altStage == ALT_FINISHED) + { + switch (horizon) + { + // East + case 0: + if (decDeviation > 0) + deviationDirection = ki18n("%1 too far high"); + else + deviationDirection = ki18n("%1 too far low"); + + break; + + // West + case 1: + if (decDeviation > 0) + deviationDirection = ki18n("%1 too far low"); + else + deviationDirection = ki18n("%1 too far high"); + break; + + default: + break; + } + } + break; // Southern hemisphere - case SOUTH_HEMISPHERE: - if (azStage == AZ_FINISHED) - { - if (decDeviation > 0) - deviationDirection = ki18n("%1 too far west"); - else - deviationDirection = ki18n("%1 too far east"); - } - else if (altStage == ALT_FINISHED) - { - switch (horizon) + case SOUTH_HEMISPHERE: + if (azStage == AZ_FINISHED) { - // East - case 0: if (decDeviation > 0) - deviationDirection = ki18n("%1 too far low"); + deviationDirection = ki18n("%1 too far west"); else - deviationDirection = ki18n("%1 too far high"); - break; - - // West - case 1: - if (decDeviation > 0) - deviationDirection = ki18n("%1 too far high"); - else - deviationDirection = ki18n("%1 too far low"); - break; - - default: - break; + deviationDirection = ki18n("%1 too far east"); } - } - break; + else if (altStage == ALT_FINISHED) + { + switch (horizon) + { + // East + case 0: + if (decDeviation > 0) + deviationDirection = ki18n("%1 too far low"); + else + deviationDirection = ki18n("%1 too far high"); + break; + + // West + case 1: + if (decDeviation > 0) + deviationDirection = ki18n("%1 too far high"); + else + deviationDirection = ki18n("%1 too far low"); + break; + + default: + break; + } + } + break; - default: - break; + default: + break; } qCDebug(KSTARS_EKOS_ALIGN) << "Polar Hemisphere is " << ((hemisphere == NORTH_HEMISPHERE) ? "North" : "South") @@ -4306,19 +4330,19 @@ dec_s.setD(dec); ra_str = QString("%1:%2:%3") - .arg(ra_s.hour(), 2, 10, QChar('0')) - .arg(ra_s.minute(), 2, 10, QChar('0')) - .arg(ra_s.second(), 2, 10, QChar('0')); + .arg(ra_s.hour(), 2, 10, QChar('0')) + .arg(ra_s.minute(), 2, 10, QChar('0')) + .arg(ra_s.second(), 2, 10, QChar('0')); if (dec_s.Degrees() < 0) dec_str = QString("-%1:%2:%3") - .arg(abs(dec_s.degree()), 2, 10, QChar('0')) - .arg(abs(dec_s.arcmin()), 2, 10, QChar('0')) - .arg(dec_s.arcsec(), 2, 10, QChar('0')); + .arg(abs(dec_s.degree()), 2, 10, QChar('0')) + .arg(abs(dec_s.arcmin()), 2, 10, QChar('0')) + .arg(dec_s.arcsec(), 2, 10, QChar('0')); else dec_str = QString("%1:%2:%3") - .arg(dec_s.degree(), 2, 10, QChar('0')) - .arg(dec_s.arcmin(), 2, 10, QChar('0')) - .arg(dec_s.arcsec(), 2, 10, QChar('0')); + .arg(dec_s.degree(), 2, 10, QChar('0')) + .arg(dec_s.arcmin(), 2, 10, QChar('0')) + .arg(dec_s.arcsec(), 2, 10, QChar('0')); } bool Align::loadAndSlew(QString fileURL) @@ -4485,13 +4509,13 @@ if (filterNum == 0) { currentFilter = nullptr; - currentFilterPosition=-1; + currentFilterPosition = -1; FilterPosCombo->clear(); return; } if (filterNum <= Filters.count()) - currentFilter = Filters.at(filterNum-1); + currentFilter = Filters.at(filterNum - 1); FilterPosCombo->clear(); @@ -4580,7 +4604,7 @@ char comment[128], error_status[512]; fitsfile *fptr = nullptr; double ra = 0, dec = 0, fits_fov_x, fits_fov_y, fov_lower, fov_upper, fits_ccd_hor_pixel = -1, - fits_ccd_ver_pixel = -1, fits_focal_length = -1; + fits_ccd_ver_pixel = -1, fits_focal_length = -1; QString fov_low, fov_high; QStringList solver_args; @@ -4699,10 +4723,10 @@ }*/ if (coord_ok && Options::astrometryUsePosition()) - solver_args << "-3" << QString::number(ra * 15.0) << "-4" << QString::number(dec) << "-5 15"; + solver_args << "-3" << QString::number(ra * 15.0) << "-4" << QString::number(dec) << "-5" << "15"; status = 0; - double pixelScale=0; + double pixelScale = 0; // If we have pixel scale in arcsecs per pixel then lets use that directly // instead of calculating it from FOCAL length and other information if (fits_read_key(fptr, TDOUBLE, "SCALE", &pixelScale, comment, &status) == 0) @@ -4787,18 +4811,18 @@ { switch (newState) { - case CAPTURE_ALIGNING: - if (currentTelescope && currentTelescope->hasAlignmentModel() && Options::resetMountModelAfterMeridian()) - { - mountModelReset = currentTelescope->clearAlignmentModel(); - qCDebug(KSTARS_EKOS_ALIGN) << "Post meridian flip mount model reset" << (mountModelReset ? "successful." : "failed."); - } + case CAPTURE_ALIGNING: + if (currentTelescope && currentTelescope->hasAlignmentModel() && Options::resetMountModelAfterMeridian()) + { + mountModelReset = currentTelescope->clearAlignmentModel(); + qCDebug(KSTARS_EKOS_ALIGN) << "Post meridian flip mount model reset" << (mountModelReset ? "successful." : "failed."); + } - QTimer::singleShot(Options::settlingTime(), this, &Ekos::Align::captureAndSolve); - break; + QTimer::singleShot(Options::settlingTime(), this, &Ekos::Align::captureAndSolve); + break; - default: - break; + default: + break; } } @@ -4982,8 +5006,9 @@ #endif // if Manual slewing is selected, don't move the mount - if (PAHManual->isChecked()){ - appendLogText(i18n("Please rotate your mount about %1deg in RA",raDiff )); + if (PAHManual->isChecked()) + { + appendLogText(i18n("Please rotate your mount about %1deg in RA", raDiff )); return; } @@ -5038,7 +5063,7 @@ RACenter.setRA(RACenter.ra0()); RACenter.setDec(RACenter.dec0()); - double PA=0; + double PA = 0; dms polarError = RACenter.angularDistanceTo(&CP, &PA); if (Options::alignmentLogging()) @@ -5117,18 +5142,22 @@ void Align::setPAHSlewDone() { emit newPAHMessage("Manual slew done."); - switch(pahStage) { - case PAH_FIRST_ROTATE : pahStage = PAH_SECOND_CAPTURE; - emit newPAHStage(pahStage); - PAHWidgets->setCurrentWidget(PAHSecondCapturePage); - appendLogText(i18n("First manual rotation done.")); - break; - case PAH_SECOND_ROTATE : pahStage = PAH_THIRD_CAPTURE; - emit newPAHStage(pahStage); - PAHWidgets->setCurrentWidget(PAHThirdCapturePage); - appendLogText(i18n("Second manual rotation done.")); - break; - default : return; // no other stage should be able to trigger this event + switch(pahStage) + { + case PAH_FIRST_ROTATE : + pahStage = PAH_SECOND_CAPTURE; + emit newPAHStage(pahStage); + PAHWidgets->setCurrentWidget(PAHSecondCapturePage); + appendLogText(i18n("First manual rotation done.")); + break; + case PAH_SECOND_ROTATE : + pahStage = PAH_THIRD_CAPTURE; + emit newPAHStage(pahStage); + PAHWidgets->setCurrentWidget(PAHThirdCapturePage); + appendLogText(i18n("Second manual rotation done.")); + break; + default : + return; // no other stage should be able to trigger this event } if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY) appendLogText(i18n("Settling...")); @@ -5185,7 +5214,7 @@ { setSolverAction(GOTO_NOTHING); appendLogText( - i18n("Mount is synced to celestial pole. You can now continue Polar Alignment Assistant procedure.")); + i18n("Mount is synced to celestial pole. You can now continue Polar Alignment Assistant procedure.")); pahStage = PAH_FIRST_CAPTURE; emit newPAHStage(pahStage); return; @@ -5451,7 +5480,7 @@ // Function adapted from https://rosettacode.org/wiki/Circles_of_given_radius_through_two_points Align::CircleSolution Align::findCircleSolutions(const QPointF &p1, const QPointF p2, double angle, - QPair &circleSolutions) + QPair &circleSolutions) { QPointF solutionOne(1, 1), solutionTwo(1, 1); @@ -5617,25 +5646,25 @@ { switch (newState) { - case ISD::Telescope::MOUNT_PARKING: - case ISD::Telescope::MOUNT_SLEWING: - case ISD::Telescope::MOUNT_MOVING: - solveB->setEnabled(false); - loadSlewB->setEnabled(false); - PAHStartB->setEnabled(false); - break; + case ISD::Telescope::MOUNT_PARKING: + case ISD::Telescope::MOUNT_SLEWING: + case ISD::Telescope::MOUNT_MOVING: + solveB->setEnabled(false); + loadSlewB->setEnabled(false); + PAHStartB->setEnabled(false); + break; - default: - if (state != ALIGN_PROGRESS) - { - solveB->setEnabled(true); - if (pahStage == PAH_IDLE) + default: + if (state != ALIGN_PROGRESS) { - PAHStartB->setEnabled(true); - loadSlewB->setEnabled(true); + solveB->setEnabled(true); + if (pahStage == PAH_IDLE) + { + PAHStartB->setEnabled(true); + loadSlewB->setEnabled(true); + } } - } - break; + break; } } @@ -5679,41 +5708,47 @@ captureAndSolve(); } } - ); + ); connect(filterManager.data(), &FilterManager::failed, [this]() { appendLogText(i18n("Filter operation failed.")); abort(); } - ); + ); connect(filterManager.data(), &FilterManager::newStatus, [this](Ekos::FilterState filterState) { if (filterPositionPending) { switch (filterState) { - case FILTER_OFFSET: - appendLogText(i18n("Changing focus offset by %1 steps...", filterManager->getTargetFilterOffset())); - break; + case FILTER_OFFSET: + appendLogText(i18n("Changing focus offset by %1 steps...", filterManager->getTargetFilterOffset())); + break; - case FILTER_CHANGE: - appendLogText(i18n("Changing filter to %1...", FilterPosCombo->itemText(filterManager->getTargetFilterPosition()-1))); - break; + case FILTER_CHANGE: + appendLogText(i18n("Changing filter to %1...", FilterPosCombo->itemText(filterManager->getTargetFilterPosition() - 1))); + break; - case FILTER_AUTOFOCUS: - appendLogText(i18n("Auto focus on filter change...")); - break; + case FILTER_AUTOFOCUS: + appendLogText(i18n("Auto focus on filter change...")); + break; - default: - break; + default: + break; } } }); - connect(filterManager.data(), &FilterManager::labelsChanged, this, [this]() { checkFilter(); }); - connect(filterManager.data(), &FilterManager::positionChanged, this, [this]() { checkFilter();}); + connect(filterManager.data(), &FilterManager::labelsChanged, this, [this]() + { + checkFilter(); + }); + connect(filterManager.data(), &FilterManager::positionChanged, this, [this]() + { + checkFilter(); + }); } QVariantMap Align::getEffectiveFOV() @@ -5798,27 +5833,27 @@ { switch (pahStage) { - case PAH_IDLE: - case PAH_FIND_CP: - default: - return introText->text(); - case PAH_FIRST_CAPTURE: - return firstCaptureText->text(); - case PAH_FIRST_ROTATE: - return firstRotateText->text(); - case PAH_SECOND_CAPTURE: - return secondCaptureText->text(); - case PAH_SECOND_ROTATE: - return secondRotateText->text(); - case PAH_THIRD_CAPTURE: - return thirdCaptureText->text(); - case PAH_STAR_SELECT: - return correctionText->text(); - case PAH_PRE_REFRESH: - case PAH_REFRESH: - return refreshText->text(); - case PAH_ERROR: - return PAHErrorDescriptionLabel->text(); + case PAH_IDLE: + case PAH_FIND_CP: + default: + return introText->text(); + case PAH_FIRST_CAPTURE: + return firstCaptureText->text(); + case PAH_FIRST_ROTATE: + return firstRotateText->text(); + case PAH_SECOND_CAPTURE: + return secondCaptureText->text(); + case PAH_SECOND_ROTATE: + return secondRotateText->text(); + case PAH_THIRD_CAPTURE: + return thirdCaptureText->text(); + case PAH_STAR_SELECT: + return correctionText->text(); + case PAH_PRE_REFRESH: + case PAH_REFRESH: + return refreshText->text(); + case PAH_ERROR: + return PAHErrorDescriptionLabel->text(); } } @@ -5837,7 +5872,7 @@ settings.insert("fw", FilterDevicesCombo->currentText()); settings.insert("filter", FilterPosCombo->currentText()); settings.insert("exp", exposureIN->value()); - settings.insert("bin", binningCombo->currentIndex()+1); + settings.insert("bin", binningCombo->currentIndex() + 1); settings.insert("solverAction", gotoModeButtonGroup->checkedId()); settings.insert("solverType", solverTypeGroup->checkedId()); settings.insert("scopeType", FOVScopeCombo->currentIndex()); @@ -5852,10 +5887,10 @@ FilterPosCombo->setCurrentText(settings["filter"].toString()); Options::setLockAlignFilterIndex(FilterPosCombo->currentIndex()); exposureIN->setValue(settings["exp"].toDouble(1)); - binningCombo->setCurrentIndex(settings["bin"].toInt()-1); + binningCombo->setCurrentIndex(settings["bin"].toInt() - 1); - gotoModeButtonGroup->button(settings["solverAction"].toInt(1))->setChecked(true); - solverTypeGroup->button(settings["solverType"].toInt(1))->setChecked(true); + gotoModeButtonGroup->button(settings["solverAction"].toInt(1))->click(); + solverTypeGroup->button(settings["solverType"].toInt(1))->click(); FOVScopeCombo->setCurrentIndex(settings["scopeType"].toInt(0)); } diff --git a/kstars/ekos/align/offlineastrometryparser.cpp b/kstars/ekos/align/offlineastrometryparser.cpp --- a/kstars/ekos/align/offlineastrometryparser.cpp +++ b/kstars/ekos/align/offlineastrometryparser.cpp @@ -360,6 +360,12 @@ { solver->terminate(); solver->disconnect(); + + // 2019-04-25: When inparallel option is enabled in astrometry.cfg + // astrometry-engine is not killed after solve-field is terminated + QProcess p; + p.start("pkill astrometry-engine"); + p.waitForFinished(); } return true; diff --git a/kstars/ekos/capture/capture.h b/kstars/ekos/capture/capture.h --- a/kstars/ekos/capture/capture.h +++ b/kstars/ekos/capture/capture.h @@ -596,6 +596,7 @@ // Jobs void resetJobs(); + void selectJob(QModelIndex i); void editJob(QModelIndex i); void resetJobEdit(); void executeJob(); @@ -710,6 +711,9 @@ // If exposure timed out, let's handle it. void processCaptureTimeout(); + // selection of a job + void selectedJobChanged(QModelIndex current, QModelIndex previous); + /* Capture */ /** diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp --- a/kstars/ekos/capture/capture.cpp +++ b/kstars/ekos/capture/capture.cpp @@ -144,6 +144,7 @@ connect(queueSaveAsB, &QPushButton::clicked, this, &Ekos::Capture::saveSequenceQueueAs); connect(queueLoadB, &QPushButton::clicked, this, static_cast(&Ekos::Capture::loadSequenceQueue)); connect(resetB, &QPushButton::clicked, this, &Ekos::Capture::resetJobs); + connect(queueTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Ekos::Capture::selectedJobChanged); connect(queueTable, &QAbstractItemView::doubleClicked, this, &Ekos::Capture::editJob); connect(queueTable, &QTableWidget::itemSelectionChanged, this, &Ekos::Capture::resetJobEdit); connect(setTemperatureB, &QPushButton::clicked, [&]() @@ -2450,6 +2451,15 @@ currentRow = queueTable->rowCount() - 1; removeJob(currentRow); + + // update selection + if (queueTable->rowCount() == 0) + return; + + if (currentRow > queueTable->rowCount()) + queueTable->selectRow(queueTable->rowCount()-1); + else + queueTable->selectRow(currentRow); } void Capture::removeJob(int index) @@ -2596,6 +2606,10 @@ qCDebug(KSTARS_EKOS_CAPTURE) << "Preparing capture job" << job->getSignature() << "for execution."; + int index = jobs.indexOf(job); + if (index >= 0) + queueTable->selectRow(index); + if (activeJob->getActiveCCD() != currentCCD) { setCamera(activeJob->getActiveCCD()->getDeviceName()); @@ -4049,15 +4063,34 @@ emit settingsUpdated(settings); } -void Capture::editJob(QModelIndex i) +void Capture::selectedJobChanged(QModelIndex current, QModelIndex previous) { + Q_UNUSED(previous); + selectJob(current); +} + +void Capture::selectJob(QModelIndex i) +{ + if (i.row() < 0 || (i.row()+1) > jobs.size()) + return; + SequenceJob * job = jobs.at(i.row()); if (job == nullptr) return; syncGUIToJob(job); + if (isBusy || jobs.size() < 2) + return; + + queueUpB->setEnabled(i.row() > 0); + queueDownB->setEnabled(i.row()+1 < jobs.size()); +} + +void Capture::editJob(QModelIndex i) +{ + selectJob(i); appendLogText(i18n("Editing job #%1...", i.row() + 1)); addToQueueB->setIcon(QIcon::fromTheme("dialog-ok-apply")); @@ -4270,10 +4303,10 @@ activeJob = nullptr; //m_TargetName.clear(); //stop(); - qDeleteAll(jobs); - jobs.clear(); while (queueTable->rowCount() > 0) queueTable->removeRow(0); + qDeleteAll(jobs); + jobs.clear(); } QString Capture::getSequenceQueueStatus() diff --git a/kstars/ekos/capture/sequencejob.cpp b/kstars/ekos/capture/sequencejob.cpp --- a/kstars/ekos/capture/sequencejob.cpp +++ b/kstars/ekos/capture/sequencejob.cpp @@ -282,7 +282,14 @@ { if (targetFilter != currentFilter) { - activeFilter->runCommand(INDI_SET_FILTER, &targetFilter); + //activeFilter->runCommand(INDI_SET_FILTER, &targetFilter); + + FilterManager::FilterPolicy policy = FilterManager::ALL_POLICIES; + // Don't perform autofocus on preview + if (isPreview()) + policy = static_cast(policy & ~FilterManager::AUTOFOCUS_POLICY); + + filterManager->setFilterPosition(targetFilter, policy); return CAPTURE_FILTER_BUSY; } } diff --git a/kstars/ekos/ekos.h b/kstars/ekos/ekos.h --- a/kstars/ekos/ekos.h +++ b/kstars/ekos/ekos.h @@ -158,7 +158,7 @@ typedef enum { SCHEDULER_IDLE, /*< Scheduler is stopped. */ SCHEDULER_STARTUP, /*< Scheduler is starting the observatory up. */ - SCHEDULER_RUNNIG, /*< Scheduler is running. */ + SCHEDULER_RUNNING, /*< Scheduler is running. */ SCHEDULER_PAUSED, /*< Scheduler is paused by the end-user. */ SCHEDULER_SHUTDOWN, /*< Scheduler is shutting the observatory down. */ SCHEDULER_ABORTED, /*< Scheduler is stopped in error. */ diff --git a/kstars/ekos/ekoslive/commands.h b/kstars/ekos/ekoslive/commands.h --- a/kstars/ekos/ekoslive/commands.h +++ b/kstars/ekos/ekoslive/commands.h @@ -22,7 +22,6 @@ GET_STATES, GET_CAMERAS, GET_MOUNTS, - GET_SCOPES, GET_FILTER_WHEELS, GET_DOMES, GET_CAPS, @@ -42,6 +41,7 @@ NEW_NOTIFICATION, NEW_TEMPERATURE, + SET_CLIENT_STATE, LOGOUT, // Profiles @@ -51,6 +51,13 @@ ADD_PROFILE, GET_PROFILE, DELETE_PROFILE, + UPDATE_PROFILE, + + // SCOPES + GET_SCOPES, + ADD_SCOPE, + DELETE_SCOPE, + UPDATE_SCOPE, // Capture CAPTURE_PREVIEW, @@ -105,7 +112,7 @@ // Polar Assistant Helper PAH_START, PAH_STOP, - PAH_REFRESH, + PAH_REFRESH, PAH_SET_CROSSHAIR, PAH_SELECT_STAR_DONE, PAH_REFRESHING_DONE, @@ -124,11 +131,10 @@ static QMap const commands = { - {GET_CONNECTION, "get_connection"}, + {GET_CONNECTION, "get_connection"}, {GET_STATES, "get_states"}, {GET_CAMERAS, "get_cameras"}, {GET_MOUNTS, "get_mounts"}, - {GET_SCOPES, "get_scopes"}, {GET_FILTER_WHEELS, "get_filter_wheels"}, {GET_DOMES, "get_domes"}, {GET_CAPS, "get_caps"}, @@ -146,16 +152,23 @@ {NEW_VIDEO_FRAME, "new_video_frame"}, {NEW_ALIGN_FRAME, "new_align_frame"}, {NEW_NOTIFICATION, "new_notification"}, - {NEW_TEMPERATURE, "new_temperature"}, + {NEW_TEMPERATURE, "new_temperature"}, + {SET_CLIENT_STATE, "set_client_state"}, {LOGOUT, "logout"}, {GET_PROFILES, "get_profiles"}, {START_PROFILE, "profile_start"}, {STOP_PROFILE, "profile_stop"}, {ADD_PROFILE, "profile_add"}, {GET_PROFILE, "profile_get"}, {DELETE_PROFILE, "profile_delete"}, + {UPDATE_PROFILE, "profile_update"}, + + {GET_SCOPES, "get_scopes"}, + {ADD_SCOPE, "scope_add"}, + {DELETE_SCOPE, "scope_delete"}, + {UPDATE_SCOPE, "scope_update"}, {CAPTURE_PREVIEW, "capture_preview"}, {CAPTURE_TOGGLE_VIDEO, "capture_toggle_video"}, @@ -203,7 +216,7 @@ {PAH_START, "polar_start"}, {PAH_STOP, "polar_stop"}, {PAH_SET_SETTINGS, "polar_set_settings"}, - {PAH_REFRESH, "polar_refresh"}, + {PAH_REFRESH, "polar_refresh"}, {PAH_SET_CROSSHAIR, "polar_set_crosshair"}, {PAH_SELECT_STAR_DONE, "polar_star_select_done"}, {PAH_REFRESHING_DONE, "polar_refreshing_done"}, diff --git a/kstars/ekos/ekoslive/ekosliveclient.cpp b/kstars/ekos/ekoslive/ekosliveclient.cpp --- a/kstars/ekos/ekoslive/ekosliveclient.cpp +++ b/kstars/ekos/ekoslive/ekosliveclient.cpp @@ -122,11 +122,19 @@ if (job->error() == false) { QJsonObject data = QJsonDocument::fromJson(dynamic_cast(job)->textData().toLatin1()).object(); - username->setText(data["username"].toString()); - password->setText(data["password"].toString()); + const QString usernameText = data["username"].toString(); + const QString passwordText = data["password"].toString(); + + // Only set and attempt connection if the data is not empty + if (usernameText.isEmpty() == false && passwordText.isEmpty() == false) + { + username->setText(usernameText); + password->setText(passwordText); + + if (autoStartCheck->isChecked()) + connectAuthServer(); + } - if (autoStartCheck->isChecked()) - connectAuthServer(); } job->deleteLater(); }); diff --git a/kstars/ekos/ekoslive/message.h b/kstars/ekos/ekoslive/message.h --- a/kstars/ekos/ekoslive/message.h +++ b/kstars/ekos/ekoslive/message.h @@ -133,6 +133,9 @@ // Options void processOptionsCommands(const QString &command, const QJsonObject &payload); + // Scopes + void processScopeCommands(const QString &command, const QJsonObject &payload); + QWebSocket m_WebSocket; QJsonObject m_AuthResponse; uint16_t m_ReconnectTries {0}; diff --git a/kstars/ekos/ekoslive/message.cpp b/kstars/ekos/ekoslive/message.cpp --- a/kstars/ekos/ekoslive/message.cpp +++ b/kstars/ekos/ekoslive/message.cpp @@ -14,8 +14,11 @@ #include "commands.h" #include "profileinfo.h" #include "indi/drivermanager.h" +#include "kstars.h" +#include "kstarsdata.h" #include "ekos_debug.h" +#include #include #include @@ -64,7 +67,7 @@ qCInfo(KSTARS_EKOS) << "Connected to Message Websocket server at" << m_URL.toDisplayString(); m_isConnected = true; - m_ReconnectTries=0; + m_ReconnectTries = 0; connect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Message::onTextReceived); @@ -87,7 +90,7 @@ { qCritical(KSTARS_EKOS) << "Websocket connection error" << m_WebSocket.errorString(); if (error == QAbstractSocket::RemoteHostClosedError || - error == QAbstractSocket::ConnectionRefusedError) + error == QAbstractSocket::ConnectionRefusedError) { if (m_ReconnectTries++ < RECONNECT_MAX_TRIES) QTimer::singleShot(RECONNECT_INTERVAL, this, SLOT(connectServer())); @@ -120,14 +123,52 @@ const QJsonObject payload = msgObj["payload"].toObject(); if (command == commands[GET_CONNECTION]) + { sendConnection(); + } else if (command == commands[LOGOUT]) { emit expired(); return; } + else if (command == commands[SET_CLIENT_STATE]) + { + // If client is connected, make sure clock is ticking + if (payload["state"].toBool(false)) + { + qCInfo(KSTARS_EKOS) << "EkosLive client is connected."; + + // If the clock is PAUSED, run it now and sync time as well. + if (KStarsData::Instance()->clock()->isActive() == false) + { + qCInfo(KSTARS_EKOS) << "Resuming and syncing clock."; + KStarsData::Instance()->clock()->start(); + QAction *a = KStars::Instance()->actionCollection()->action("time_to_now"); + if (a) + a->trigger(); + } + } + // Otherwise, if KStars was started in PAUSED state + // then we pause here as well to save power. + else + { + qCInfo(KSTARS_EKOS) << "EkosLive client is disconnected."; + // It was started with paused state, so let's pause IF Ekos is not running + if (KStars::Instance()->isStartedWithClockRunning() == false && m_Manager->ekosStatus() == Ekos::CommunicationStatus::Idle) + { + qCInfo(KSTARS_EKOS) << "Stopping the clock."; + KStarsData::Instance()->clock()->stop(); + } + } + } + else if (command == commands[GET_DRIVERS]) + sendDrivers(); else if (command == commands[GET_PROFILES]) sendProfiles(); + else if (command == commands[GET_SCOPES]) + sendScopes(); + else if (command.startsWith("scope_")) + processScopeCommands(command, payload); else if (command.startsWith("profile_")) processProfileCommands(command, payload); @@ -140,16 +181,12 @@ sendCameras(); else if (command == commands[GET_MOUNTS]) sendMounts(); - else if (command == commands[GET_SCOPES]) - sendScopes(); else if (command == commands[GET_FILTER_WHEELS]) sendFilterWheels(); else if (command == commands[GET_DOMES]) sendDomes(); else if (command == commands[GET_CAPS]) sendCaps(); - else if (command == commands[GET_DRIVERS]) - sendDrivers(); else if (command.startsWith("capture_")) processCaptureCommands(command, payload); else if (command.startsWith("mount_")) @@ -183,11 +220,12 @@ connect(oneCCD, &ISD::CCD::newTemperatureValue, this, &Message::sendTemperature, Qt::UniqueConnection); ISD::CCDChip *primaryChip = oneCCD->getChip(ISD::CCDChip::PRIMARY_CCD); - double temperature=Ekos::INVALID_VALUE, gain=Ekos::INVALID_VALUE; + double temperature = Ekos::INVALID_VALUE, gain = Ekos::INVALID_VALUE; oneCCD->getTemperature(&temperature); oneCCD->getGain(&gain); - QJsonObject oneCamera = { + QJsonObject oneCamera = + { {"name", oneCCD->getDeviceName()}, {"canBin", primaryChip->canBin()}, {"hasTemperature", oneCCD->hasCooler()}, @@ -216,7 +254,8 @@ { ISD::Telescope *oneTelescope = dynamic_cast(gd); - QJsonObject oneMount = { + QJsonObject oneMount = + { {"name", oneTelescope->getDeviceName()}, {"canPark", oneTelescope->canPark()}, {"canSync", oneTelescope->canSync()}, @@ -252,7 +291,8 @@ { ISD::Dome *dome = dynamic_cast(gd); - QJsonObject oneDome = { + QJsonObject oneDome = + { {"name", dome->getDeviceName()}, {"canPark", dome->canPark()}, {"canGoto", dome->canAbsMove()}, @@ -291,7 +331,8 @@ { ISD::DustCap *dustCap = dynamic_cast(gd); - QJsonObject oneCap = { + QJsonObject oneCap = + { {"name", dustCap->getDeviceName()}, {"canPark", dustCap->canPark()}, {"hasLight", dustCap->hasLight()}, @@ -306,28 +347,34 @@ void Message::sendDrivers() { - if (m_isConnected == false) - return; + if (m_isConnected == false) + return; - sendResponse(commands[GET_DRIVERS], DriverManager::Instance()->getDriverList()); + sendResponse(commands[GET_DRIVERS], DriverManager::Instance()->getDriverList()); } void Message::sendScopes() { - if (m_isConnected == false || - m_Manager->getEkosStartingStatus() != Ekos::Success || - m_Manager->mountModule() == nullptr) + if (m_isConnected == false) return; - QJsonArray scopeList = m_Manager->mountModule()->getScopes(); + QJsonArray scopeList; + + QList allScopes; + KStarsData::Instance()->userdb()->GetAllScopes(allScopes); + + for (auto &scope : allScopes) + scopeList.append(scope->toJson()); + sendResponse(commands[GET_SCOPES], scopeList); } void Message::sendTemperature(double value) { ISD::CCD *oneCCD = dynamic_cast(sender()); - QJsonObject temperature = { + QJsonObject temperature = + { {"name", oneCCD->getDeviceName()}, {"value", value} }; @@ -353,10 +400,11 @@ break; QJsonArray filters; - for (int i=0; i < filterNames->ntp; i++) + for (int i = 0; i < filterNames->ntp; i++) filters.append(filterNames->tp[i].text); - QJsonObject oneFilter = { + QJsonObject oneFilter = + { {"name", gd->getDeviceName()}, {"filters", filters} }; @@ -369,7 +417,7 @@ void Message::setCaptureSettings(const QJsonObject &settings) { - m_Manager->captureModule()->setSettings(settings); + m_Manager->captureModule()->setSettings(settings); } void Message::processCaptureCommands(const QString &command, const QJsonObject &payload) @@ -495,7 +543,7 @@ { QString direction = payload["direction"].toString(); ISD::Telescope::TelescopeMotionCommand action = payload["action"].toBool(false) ? - ISD::Telescope::MOTION_START : ISD::Telescope::MOTION_STOP; + ISD::Telescope::MOTION_START : ISD::Telescope::MOTION_STOP; if (direction == "N") mount->motionCommand(action, ISD::Telescope::MOTION_NORTH, -1); @@ -563,7 +611,8 @@ if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success) return; - QJsonObject alignState = { + QJsonObject alignState = + { {"status", Ekos::alignStates[newState]} }; @@ -575,7 +624,8 @@ if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success) return; - QJsonObject alignState = { + QJsonObject alignState = + { {"solution", QJsonObject::fromVariantMap(solution)}, }; @@ -621,11 +671,11 @@ // #2 Find fraction of the dimensions above the full image size // Add to it the bounding rect top left offsets - x = (boundX+boundingRect.x()) / viewSize.width(); - y = (boundY+boundingRect.y()) / viewSize.height(); + x = (boundX + boundingRect.x()) / viewSize.width(); + y = (boundY + boundingRect.y()) / viewSize.height(); } - align->setPAHCorrectionOffsetPercentage(x,y); + align->setPAHCorrectionOffsetPercentage(x, y); } else if (command == commands[PAH_SELECT_STAR_DONE]) { @@ -645,7 +695,8 @@ Q_UNUSED(stage); Ekos::Align *align = m_Manager->alignModule(); - QJsonObject polarState = { + QJsonObject polarState = + { {"stage", align->getPAHStage()} }; @@ -664,7 +715,8 @@ QTextDocument doc; doc.setHtml(message); - QJsonObject polarState = { + QJsonObject polarState = + { {"message", doc.toPlainText()} }; @@ -679,15 +731,17 @@ this->correctionVector = correctionVector; QPointF center = 0.5 * correctionVector.p1() + 0.5 * correctionVector.p2(); - QJsonObject vector = { + QJsonObject vector = + { {"center_x", center.x()}, {"center_y", center.y()}, {"mag", correctionVector.length()}, {"pa", correctionVector.angle()}, {"error", polarError} }; - QJsonObject polarState = { + QJsonObject polarState = + { {"vector", vector} }; @@ -699,7 +753,8 @@ if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success) return; - QJsonObject polarState = { + QJsonObject polarState = + { {"enabled", enabled} }; @@ -720,25 +775,33 @@ else if (command == commands[ADD_PROFILE]) { m_Manager->addNamedProfile(payload); + sendProfiles(); + } + else if (command == commands[UPDATE_PROFILE]) + { + m_Manager->editNamedProfile(payload); + sendProfiles(); } else if (command == commands[GET_PROFILE]) { m_Manager->getNamedProfile(payload["name"].toString()); } else if (command == commands[DELETE_PROFILE]) { m_Manager->deleteNamedProfile(payload["name"].toString()); + sendProfiles(); } } void Message::sendProfiles() { QJsonArray profileArray; - for (const auto &oneProfile: m_Manager->profiles) + for (const auto &oneProfile : m_Manager->profiles) profileArray.append(oneProfile->toJson()); - QJsonObject profiles = { + QJsonObject profiles = + { {"selectedProfile", m_Manager->getCurrentProfile()->name}, {"profiles", profileArray} }; @@ -750,7 +813,8 @@ if (status == Ekos::Pending) return; - QJsonObject connectionState = { + QJsonObject connectionState = + { {"connected", true}, {"online", status == Ekos::Success} }; @@ -771,14 +835,34 @@ emit optionsChanged(m_Options); } +void Message::processScopeCommands(const QString &command, const QJsonObject &payload) +{ + if (command == commands[ADD_SCOPE]) + { + KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(), payload["driver"].toString(), + payload["type"].toString(), payload["focal_length"].toDouble(), payload["aperture"].toDouble()); + } + else if (command == commands[UPDATE_SCOPE]) + { + KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(), payload["driver"].toString(), + payload["type"].toString(), payload["focal_length"].toDouble(), payload["aperture"].toDouble(), payload["id"].toString()); + } + else if (command == commands[DELETE_SCOPE]) + { + KStarsData::Instance()->userdb()->DeleteEquipment("telescope", payload["id"].toInt()); + } + + sendScopes(); +} + void Message::sendResponse(const QString &command, const QJsonObject &payload) { - m_WebSocket.sendTextMessage(QJsonDocument({{"type",command},{"payload",payload}}).toJson(QJsonDocument::Compact)); + m_WebSocket.sendTextMessage(QJsonDocument({{"type", command}, {"payload", payload}}).toJson(QJsonDocument::Compact)); } void Message::sendResponse(const QString &command, const QJsonArray &payload) { - m_WebSocket.sendTextMessage(QJsonDocument({{"type",command},{"payload",payload}}).toJson(QJsonDocument::Compact)); + m_WebSocket.sendTextMessage(QJsonDocument({{"type", command}, {"payload", payload}}).toJson(QJsonDocument::Compact)); } void Message::updateMountStatus(const QJsonObject &status) @@ -834,7 +918,8 @@ if (m_isConnected == false) return; - QJsonObject connectionState = { + QJsonObject connectionState = + { {"connected", true}, {"online", m_Manager->getEkosStartingStatus() == Ekos::Success} }; @@ -855,7 +940,8 @@ if (m_Manager->mountModule()) { - QJsonObject mountState = { + QJsonObject mountState = + { {"status", m_Manager->mountStatus->text()}, {"target", m_Manager->mountTarget->text()}, {"slewRate", m_Manager->mountModule()->slewRate()} @@ -873,7 +959,8 @@ if (m_Manager->alignModule()) { // Align State - QJsonObject alignState = { + QJsonObject alignState = + { {"status", Ekos::alignStates[m_Manager->alignModule()->status()]}, {"solvers", QJsonArray::fromStringList(m_Manager->alignModule()->getActiveSolvers())} }; @@ -885,7 +972,8 @@ // Polar State QTextDocument doc; doc.setHtml(m_Manager->alignModule()->getPAHMessage()); - QJsonObject polarState = { + QJsonObject polarState = + { {"stage", m_Manager->alignModule()->getPAHStage()}, {"enabled", m_Manager->alignModule()->isPAHEnabled()}, {"message", doc.toPlainText()}, @@ -902,7 +990,7 @@ if (m_isConnected == false || m_Options[OPTION_SET_NOTIFICATIONS] == false) return; - QJsonObject newEvent = {{ "severity", event}, {"message", message},{"uuid",QUuid::createUuid().toString()}}; + QJsonObject newEvent = {{ "severity", event}, {"message", message}, {"uuid", QUuid::createUuid().toString()}}; sendResponse(commands[NEW_NOTIFICATION], newEvent); } diff --git a/kstars/ekos/focus/focus.h b/kstars/ekos/focus/focus.h --- a/kstars/ekos/focus/focus.h +++ b/kstars/ekos/focus/focus.h @@ -31,525 +31,567 @@ */ class Focus : public QWidget, public Ui::Focus { - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Focus") - Q_PROPERTY(Ekos::FocusState status READ status NOTIFY newStatus) - Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) - Q_PROPERTY(QString camera READ camera WRITE setCamera) - Q_PROPERTY(QString focuser READ focuser WRITE setFocuser) - Q_PROPERTY(QString filterWheel READ filterWheel WRITE setFilterWheel) - Q_PROPERTY(QString filter READ filter WRITE setFilter) - Q_PROPERTY(double HFR READ getHFR NOTIFY newHFR) - Q_PROPERTY(double exposure READ exposure WRITE setExposure) - - public: - Focus(); - ~Focus(); - - typedef enum { FOCUS_NONE, FOCUS_IN, FOCUS_OUT } FocusDirection; - typedef enum { FOCUS_MANUAL, FOCUS_AUTO } FocusType; - typedef enum { FOCUS_ITERATIVE, FOCUS_POLYNOMIAL } FocusAlgorithm; - - /** @defgroup FocusDBusInterface Ekos DBus Interface - Focus Module - * Ekos::Focus interface provides advanced scripting capabilities to perform manual and automatic focusing operations. - */ - - /*@{*/ - - /** 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 focuser device from the available focuser drivers. The focuser device can be the same as the CCD driver if the focuser functionality was embedded within the driver. - * @param device The focuser device name - * @return Returns true if focuser device is found and set, false otherwise. - */ - Q_SCRIPTABLE bool setFocuser(const QString &device); - Q_SCRIPTABLE QString focuser(); - - /** DBUS interface function. - * select the filter device from the available filter drivers. The filter device can be the same as the CCD driver if the filter functionality was embedded within the driver. - * @param device The filter device name - * @return Returns true if filter device is found and set, false otherwise. - */ - Q_SCRIPTABLE bool setFilterWheel(const QString &device); - Q_SCRIPTABLE QString filterWheel(); - - /** DBUS interface function. - * select the filter from the available filters. - * @param filter The filter name - * @return Returns true if filter is found and set, false otherwise. - */ - Q_SCRIPTABLE bool setFilter(const QString &filter); - Q_SCRIPTABLE QString filter(); - - /** DBUS interface function. - * @return Returns True if current focuser supports auto-focusing - */ - Q_SCRIPTABLE bool canAutoFocus() { return (focusType == FOCUS_AUTO); } - - /** DBUS interface function. - * @return Returns Half-Flux-Radius in pixels. - */ - Q_SCRIPTABLE double getHFR() { return currentHFR; } - - /** DBUS interface function. - * Set CCD exposure value - * @param value exposure value in seconds. - */ - Q_SCRIPTABLE Q_NOREPLY void setExposure(double value); - Q_SCRIPTABLE double exposure() { return exposureIN->value(); } - - /** DBUS interface function. - * Set CCD binning - * @param binX horizontal binning - * @param binY vertical binning - */ - Q_SCRIPTABLE Q_NOREPLY void setBinning(int binX, int binY); - - /** 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 Auto Focus options. The options must be set before starting the autofocus 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 focus star in the frame. If it fails to select a star, the user will be asked to select a star manually. - */ - Q_SCRIPTABLE Q_NOREPLY void setAutoStarEnabled(bool enable); - - /** DBUS interface function. - * Set Auto Focus options. The options must be set before starting the autofocus operation. If no options are set, the options loaded from the user configuration are used. - * @param enable if true, Ekos will capture a subframe around the selected focus star. The subframe size is determined by the boxSize parameter. - */ - Q_SCRIPTABLE Q_NOREPLY void setAutoSubFrameEnabled(bool enable); - - /** DBUS interface function. - * Set Autofocus parameters - * @param boxSize the box size around the focus star in pixels. The boxsize is used to subframe around the focus star. - * @param stepSize the initial step size to be commanded to the focuser. If the focuser is absolute, the step size is in ticks. For relative focusers, the focuser will be commanded to focus inward for stepSize milliseconds initially. - * @param maxTravel the maximum steps permitted before the autofocus operation aborts. - * @param tolerance Measure of how accurate the autofocus algorithm is. If the difference between the current HFR and minimum measured HFR is less than %tolerance after the focuser traversed both ends of the V-curve, then the focusing operation - * is deemed successful. Otherwise, the focusing operation will continue. - */ - Q_SCRIPTABLE Q_NOREPLY void setAutoFocusParameters(int boxSize, int stepSize, int maxTravel, double tolerance); - - /** DBUS interface function. - * resetFrame Resets the CCD frame to its full native resolution. - */ - Q_SCRIPTABLE Q_NOREPLY void resetFrame(); - - /** DBUS interface function. - * Return state of Focuser modue (Ekos::FocusState) - */ - - Q_SCRIPTABLE Ekos::FocusState status() { return state; } - - /** @}*/ - - /** - * @brief Add CCD to the list of available CCD. - * @param newCCD pointer to CCD device. - */ - void addCCD(ISD::GDInterface *newCCD); - - /** - * @brief addFocuser Add focuser to the list of available focusers. - * @param newFocuser pointer to focuser device. - */ - void addFocuser(ISD::GDInterface *newFocuser); - - /** - * @brief addFilter Add filter to the list of available filters. - * @param newFilter pointer to filter device. - */ - void addFilter(ISD::GDInterface *newFilter); - - /** - * @brief removeDevice Remove device from Focus module - * @param deviceRemoved pointer to device - */ - void removeDevice(ISD::GDInterface *deviceRemoved); - - void setFilterManager(const QSharedPointer &manager); - - void clearLog(); - QStringList logText() { return m_LogText; } - QString getLogText() { return m_LogText.join("\n"); } - - public slots: - - /** \addtogroup FocusDBusInterface - * @{ - */ - - /* Focus */ - /** DBUS interface function. - * Start the autofocus operation. - */ - Q_SCRIPTABLE Q_NOREPLY void start(); - - /** DBUS interface function. - * Abort the autofocus operation. - */ - Q_SCRIPTABLE Q_NOREPLY void abort(); - - /** DBUS interface function. - * Capture a focus frame. - */ - Q_SCRIPTABLE Q_NOREPLY void capture(); - - /** DBUS interface function. - * Focus inward - * @param ms If set, focus inward for ms ticks (Absolute Focuser), or ms milliseconds (Relative Focuser). If not set, it will use the value specified in the options. - */ - Q_SCRIPTABLE bool focusIn(int ms = -1); - - /** DBUS interface function. - * Focus outward - * @param ms If set, focus outward for ms ticks (Absolute Focuser), or ms milliseconds (Relative Focuser). If not set, it will use the value specified in the options. - */ - Q_SCRIPTABLE bool focusOut(int ms = -1); - - /** @}*/ - - /** - * @brief startFraming Begins continuous capture of the CCD and calculates HFR every frame. - */ - void startFraming(); - - /** - * @brief checkStopFocus Perform checks before stopping the autofocus operation. Some checks are necessary for in-sequence focusing. - */ - void checkStopFocus(); - - /** - * @brief Check CCD and make sure information is updated accordingly. This simply calls syncCCDInfo for the current CCD. - * @param CCDNum By default, we check the already selected CCD in the dropdown menu. If CCDNum is specified, the check is made against this specific CCD in the dropdown menu. - * CCDNum is the index of the CCD in the dropdown menu. - */ - void checkCCD(int CCDNum = -1); - - /** - * @brief syncCCDInfo Read current CCD information and update settings accordingly. - */ - void syncCCDInfo(); - - /** - * @brief Check Focuser and make sure information is updated accordingly. - * @param FocuserNum By default, we check the already selected focuser in the dropdown menu. If FocuserNum is specified, the check is made against this specific focuser in the dropdown menu. - * FocuserNum is the index of the focuser in the dropdown menu. - */ - void checkFocuser(int FocuserNum = -1); - - /** - * @brief Check Filter and make sure information is updated accordingly. - * @param filterNum By default, we check the already selected filter in the dropdown menu. If filterNum is specified, the check is made against this specific filter in the dropdown menu. - * filterNum is the index of the filter in the dropdown menu. - */ - void checkFilter(int filterNum = -1); - - /** - * @brief clearDataPoints Remove all data points from HFR plots - */ - void clearDataPoints(); - - /** - * @brief focusStarSelected The user selected a focus star, save its coordinates and subframe it if subframing is enabled. - * @param x X coordinate - * @param y Y coordinate - */ - void focusStarSelected(int x, int y); - - /** - * @brief newFITS A new FITS blob is received by the CCD driver. - * @param bp pointer to blob data - */ - void newFITS(IBLOB *bp); - - /** - * @brief processFocusNumber Read focus number properties of interest as they arrive from the focuser driver and process them accordingly. - * @param nvp pointer to updated focuser number property. - */ - void processFocusNumber(INumberVectorProperty *nvp); - - /** - * @brief checkFocus Given the minimum required HFR, check focus and calculate HFR. If current HFR exceeds required HFR, start autofocus process, otherwise do nothing. - * @param requiredHFR Minimum HFR to trigger autofocus process. - */ - void checkFocus(double requiredHFR); - - /** - * @brief setFocusStatus Upon completion of the focusing process, set its status (fail or pass) and reset focus process to clean state. - * @param status If true, the focus process finished successfully. Otherwise, it failed. - */ - void setAutoFocusResult(bool status); - - /** - * @brief filterChangeWarning Warn the user it is not a good idea to apply image filter in the filter process as they can skew the HFR calculations. - * @param index Index of image filter selected by the user. - */ - void filterChangeWarning(int index); - - // Log - void appendLogText(const QString &); - - // Adjust focuser offset, relative or absolute - void adjustFocusOffset(int value, bool useAbsoluteOffset); - - // Update Mount module status - void setMountStatus(ISD::Telescope::Status newState); - - /** - * @brief toggleVideo Turn on and off video streaming if supported by the camera. - * @param enabled Set to true to start video streaming, false to stop it if active. - */ - void toggleVideo(bool enabled); - - private slots: - /** - * @brief toggleSubframe Process enabling and disabling subfrag. - * @param enable If true, subframing is enabled. If false, subframing is disabled. Even if subframing is enabled, it must be supported by the CCD driver. - */ - void toggleSubframe(bool enable); - - void checkAutoStarTimeout(); - - void setAbsoluteFocusTicks(); - - void setActiveBinning(int bin); - - void setDefaultCCD(QString ccd); - void setDefaultFocuser(QString focuser); - - void updateBoxSize(int value); - - void setThreshold(double value); - - void processCaptureTimeout(); - - void setCaptureComplete(); - - void showFITSViewer(); - - void toggleFocusingWidgetFullScreen(); - - void setVideoStreamEnabled(bool enabled); - - signals: - void newLog(const QString &text); - void newStatus(Ekos::FocusState state); - void newHFR(double hfr, int position); - - void absolutePositionChanged(int value); - void focusPositionAdjusted(); - - void suspendGuiding(); - void resumeGuiding(); - void newStarPixmap(QPixmap &); - void newProfilePixmap(QPixmap &); - - private: - void drawHFRPlot(); - void drawProfilePlot(); - void getAbsFocusPosition(); - void autoFocusAbs(); - void autoFocusRel(); - void resetButtons(); - void stop(bool aborted = false); - bool findMinimum(double expected, double *position, double *hfr); - static double fn1(double x, void *params); - - /** - * @brief syncTrackingBoxPosition Sync the tracking box to the current selected star center - */ - void syncTrackingBoxPosition(); - - /// Focuser device needed for focus operation - ISD::Focuser *currentFocuser { nullptr }; - /// CCD device needed for focus operation - ISD::CCD *currentCCD { nullptr }; - - /// Optional device filter - ISD::GDInterface *currentFilter { nullptr }; - /// Current filter position - int currentFilterPosition { -1 }; - int fallbackFilterPosition { -1 }; - /// True if we need to change filter position and wait for result before continuing capture - bool filterPositionPending { false }; - bool fallbackFilterPending { false }; - - /// List of Focusers - QList Focusers; - /// List of CCDs - QList CCDs; - /// They're generic GDInterface because they could be either ISD::CCD or ISD::Filter - QList Filters; - - /// As the name implies - FocusDirection lastFocusDirection { FOCUS_NONE }; - /// What type of focusing are we doing right now? - FocusType focusType { FOCUS_MANUAL }; - /// Focus HFR & Centeroid algorithms - StarAlgorithm focusDetection { ALGORITHM_GRADIENT }; - /// Focus Process Algorithm - FocusAlgorithm focusAlgorithm { FOCUS_ITERATIVE }; - - /********************* - * HFR Club variables - *********************/ - - /// Current HFR value just fetched from FITS file - double currentHFR { 0 }; - /// Last HFR value recorded - double lastHFR { 0 }; - /// If (currentHFR > deltaHFR) we start the autofocus process. - double minimumRequiredHFR { -1 }; - /// Maximum HFR recorded - double maxHFR { 1 }; - /// Is HFR increasing? We're going away from the sweet spot! If HFRInc=1, we re-capture just to make sure HFR calculations are correct, if HFRInc > 1, we switch directions - int HFRInc { 0 }; - /// If HFR decreasing? Well, good job. Once HFR start decreasing, we can start calculating HFR slope and estimating our next move. - int HFRDec { 0 }; - - /**************************** - * Absolute position focusers - ****************************/ - /// Absolute focus position - double currentPosition { 0 }; - /// What was our position before we started the focus process? - int initialFocuserAbsPosition { -1 }; - /// Pulse duration in ms for relative focusers that only support timers, or the number of ticks in a relative or absolute focuser - int pulseDuration { 1000 }; - /// Does the focuser support absolute motion? - bool canAbsMove { false }; - /// Does the focuser support relative motion? - bool canRelMove { false }; - /// Does the focuser support timer-based motion? - bool canTimerMove { false }; - /// Maximum range of motion for our lovely absolute focuser - double absMotionMax { 0 }; - /// Minimum range of motion for our lovely absolute focuser - double absMotionMin { 0 }; - /// How many iterations have we completed now in our absolute autofocus algorithm? We can't go forever - int absIterations { 0 }; - - /**************************** - * Misc. variables - ****************************/ - - /// Are we in the process of capturing an image? - bool captureInProgress { false }; - // Was the frame modified by us? Better keep track since we need to return it to its previous state once we are done with the focus operation. - //bool frameModified; - /// Was the modified frame subFramed? - bool subFramed { false }; - /// If the autofocus process fails, let's not ruin the capture session probably taking place in the next tab. Instead, we should restart it and try again, but we keep count until we hit MAXIMUM_RESET_ITERATIONS - /// and then we truly give up. - int resetFocusIteration { 0 }; - /// Which filter must we use once the autofocus process kicks in? - int lockedFilterIndex { -1 }; - /// Keep track of what we're doing right now - bool inAutoFocus { false }; - bool inFocusLoop { false }; - bool inSequenceFocus { false }; - bool resetFocus { false }; - /// Did we reverse direction? - bool reverseDir { false }; - /// Did the user or the auto selection process finish selecting our focus star? - bool starSelected { false }; - /// Adjust the focus position to a target value - bool adjustFocus { false }; - // Target frame dimensions - //int fx,fy,fw,fh; - /// If HFR=-1 which means no stars detected, we need to decide how many times should the re-capture process take place before we give up or reverse direction. - int noStarCount { 0 }; - /// Track which upload mode the CCD is set to. If set to UPLOAD_LOCAL, then we need to switch it to UPLOAD_CLIENT in order to do focusing, and then switch it back to UPLOAD_LOCAL - ISD::CCD::UploadMode rememberUploadMode { ISD::CCD::UPLOAD_CLIENT }; - /// Previous binning setting - int activeBin { 0 }; - /// HFR values for captured frames before averages - QVector HFRFrames; - // CCD Exposure Looping - bool rememberCCDExposureLooping = { false }; - - QStringList m_LogText; - ITextVectorProperty *filterName { nullptr }; - INumberVectorProperty *filterSlot { nullptr }; - - /**************************** - * Plot variables - ****************************/ - - /// Plot minimum positions - double minPos { 1e6 }; - /// Plot maximum positions - double maxPos { 0 }; - /// List of V curve plot points - /// V-Curve graph - QCPGraph *v_graph { nullptr }; - - // Last gaussian fit values - QVector lastGausIndexes; - QVector lastGausFrequencies; - QCPGraph *currentGaus { nullptr }; - QCPGraph *firstGaus { nullptr }; - QCPGraph *lastGaus { nullptr }; - - QVector hfr_position, hfr_value; - - // Pixmaps - QPixmap profilePixmap; - - /// State - Ekos::FocusState state { Ekos::FOCUS_IDLE }; - - /// FITS Scale - FITSScale defaultScale; - - /// CCD Chip frame settings - QMap frameSettings; - - /// Selected star coordinates - QVector3D starCenter; - - // Remember last star center coordinates in case of timeout in manual select mode - QVector3D rememberStarCenter; - - /// Focus Frame - FITSView *focusView { nullptr }; - - /// Star Select Timer - QTimer waitStarSelectTimer; - - /// FITS Viewer in case user want to display in it instead of internal view - QPointer fv; - - /// Track star position and HFR to know if we're detecting bogus stars due to detection algorithm false positive results - QVector starsHFR; - - /// Relative Profile - QCustomPlot *profilePlot { nullptr }; - QDialog *profileDialog { nullptr }; - - /// Polynomial fitting coefficients - std::vector coeff; - int polySolutionFound { 0 }; - - // Capture timeout timer - QTimer captureTimeout; - uint8_t captureTimeoutCounter { 0 }; - - // Guide Suspend - bool m_GuidingSuspended { false }; - - // Filter Manager - QSharedPointer filterManager; + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Focus") + Q_PROPERTY(Ekos::FocusState status READ status NOTIFY newStatus) + Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) + Q_PROPERTY(QString camera READ camera WRITE setCamera) + Q_PROPERTY(QString focuser READ focuser WRITE setFocuser) + Q_PROPERTY(QString filterWheel READ filterWheel WRITE setFilterWheel) + Q_PROPERTY(QString filter READ filter WRITE setFilter) + Q_PROPERTY(double HFR READ getHFR NOTIFY newHFR) + Q_PROPERTY(double exposure READ exposure WRITE setExposure) + + public: + Focus(); + ~Focus(); + + typedef enum { FOCUS_NONE, FOCUS_IN, FOCUS_OUT } FocusDirection; + typedef enum { FOCUS_MANUAL, FOCUS_AUTO } FocusType; + typedef enum { FOCUS_ITERATIVE, FOCUS_POLYNOMIAL } FocusAlgorithm; + + /** @defgroup FocusDBusInterface Ekos DBus Interface - Focus Module + * Ekos::Focus interface provides advanced scripting capabilities to perform manual and automatic focusing operations. + */ + + /*@{*/ + + /** 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 focuser device from the available focuser drivers. The focuser device can be the same as the CCD driver if the focuser functionality was embedded within the driver. + * @param device The focuser device name + * @return Returns true if focuser device is found and set, false otherwise. + */ + Q_SCRIPTABLE bool setFocuser(const QString &device); + Q_SCRIPTABLE QString focuser(); + + /** DBUS interface function. + * select the filter device from the available filter drivers. The filter device can be the same as the CCD driver if the filter functionality was embedded within the driver. + * @param device The filter device name + * @return Returns true if filter device is found and set, false otherwise. + */ + Q_SCRIPTABLE bool setFilterWheel(const QString &device); + Q_SCRIPTABLE QString filterWheel(); + + /** DBUS interface function. + * select the filter from the available filters. + * @param filter The filter name + * @return Returns true if filter is found and set, false otherwise. + */ + Q_SCRIPTABLE bool setFilter(const QString &filter); + Q_SCRIPTABLE QString filter(); + + /** DBUS interface function. + * @return Returns True if current focuser supports auto-focusing + */ + Q_SCRIPTABLE bool canAutoFocus() + { + return (focusType == FOCUS_AUTO); + } + + /** DBUS interface function. + * @return Returns Half-Flux-Radius in pixels. + */ + Q_SCRIPTABLE double getHFR() + { + return currentHFR; + } + + /** DBUS interface function. + * Set CCD exposure value + * @param value exposure value in seconds. + */ + Q_SCRIPTABLE Q_NOREPLY void setExposure(double value); + Q_SCRIPTABLE double exposure() + { + return exposureIN->value(); + } + + /** DBUS interface function. + * Set CCD binning + * @param binX horizontal binning + * @param binY vertical binning + */ + Q_SCRIPTABLE Q_NOREPLY void setBinning(int binX, int binY); + + /** 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 Auto Focus options. The options must be set before starting the autofocus 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 focus star in the frame. If it fails to select a star, the user will be asked to select a star manually. + */ + Q_SCRIPTABLE Q_NOREPLY void setAutoStarEnabled(bool enable); + + /** DBUS interface function. + * Set Auto Focus options. The options must be set before starting the autofocus operation. If no options are set, the options loaded from the user configuration are used. + * @param enable if true, Ekos will capture a subframe around the selected focus star. The subframe size is determined by the boxSize parameter. + */ + Q_SCRIPTABLE Q_NOREPLY void setAutoSubFrameEnabled(bool enable); + + /** DBUS interface function. + * Set Autofocus parameters + * @param boxSize the box size around the focus star in pixels. The boxsize is used to subframe around the focus star. + * @param stepSize the initial step size to be commanded to the focuser. If the focuser is absolute, the step size is in ticks. For relative focusers, the focuser will be commanded to focus inward for stepSize milliseconds initially. + * @param maxTravel the maximum steps permitted before the autofocus operation aborts. + * @param tolerance Measure of how accurate the autofocus algorithm is. If the difference between the current HFR and minimum measured HFR is less than %tolerance after the focuser traversed both ends of the V-curve, then the focusing operation + * is deemed successful. Otherwise, the focusing operation will continue. + */ + Q_SCRIPTABLE Q_NOREPLY void setAutoFocusParameters(int boxSize, int stepSize, int maxTravel, double tolerance); + + /** DBUS interface function. + * resetFrame Resets the CCD frame to its full native resolution. + */ + Q_SCRIPTABLE Q_NOREPLY void resetFrame(); + + /** DBUS interface function. + * Return state of Focuser modue (Ekos::FocusState) + */ + + Q_SCRIPTABLE Ekos::FocusState status() + { + return state; + } + + /** @}*/ + + /** + * @brief Add CCD to the list of available CCD. + * @param newCCD pointer to CCD device. + */ + void addCCD(ISD::GDInterface *newCCD); + + /** + * @brief addFocuser Add focuser to the list of available focusers. + * @param newFocuser pointer to focuser device. + */ + void addFocuser(ISD::GDInterface *newFocuser); + + /** + * @brief addFilter Add filter to the list of available filters. + * @param newFilter pointer to filter device. + */ + void addFilter(ISD::GDInterface *newFilter); + + /** + * @brief removeDevice Remove device from Focus module + * @param deviceRemoved pointer to device + */ + void removeDevice(ISD::GDInterface *deviceRemoved); + + void setFilterManager(const QSharedPointer &manager); + + void clearLog(); + QStringList logText() + { + return m_LogText; + } + QString getLogText() + { + return m_LogText.join("\n"); + } + + public slots: + + /** \addtogroup FocusDBusInterface + * @{ + */ + + /* Focus */ + /** DBUS interface function. + * Start the autofocus operation. + */ + Q_SCRIPTABLE Q_NOREPLY void start(); + + /** DBUS interface function. + * Abort the autofocus operation. + */ + Q_SCRIPTABLE Q_NOREPLY void abort(); + + /** DBUS interface function. + * Capture a focus frame. + */ + Q_SCRIPTABLE Q_NOREPLY void capture(); + + /** DBUS interface function. + * Focus inward + * @param ms If set, focus inward for ms ticks (Absolute Focuser), or ms milliseconds (Relative Focuser). If not set, it will use the value specified in the options. + */ + Q_SCRIPTABLE bool focusIn(int ms = -1); + + /** DBUS interface function. + * Focus outward + * @param ms If set, focus outward for ms ticks (Absolute Focuser), or ms milliseconds (Relative Focuser). If not set, it will use the value specified in the options. + */ + Q_SCRIPTABLE bool focusOut(int ms = -1); + + /** @}*/ + + /** + * @brief startFraming Begins continuous capture of the CCD and calculates HFR every frame. + */ + void startFraming(); + + /** + * @brief checkStopFocus Perform checks before stopping the autofocus operation. Some checks are necessary for in-sequence focusing. + */ + void checkStopFocus(); + + /** + * @brief Check CCD and make sure information is updated accordingly. This simply calls syncCCDInfo for the current CCD. + * @param CCDNum By default, we check the already selected CCD in the dropdown menu. If CCDNum is specified, the check is made against this specific CCD in the dropdown menu. + * CCDNum is the index of the CCD in the dropdown menu. + */ + void checkCCD(int CCDNum = -1); + + /** + * @brief syncCCDInfo Read current CCD information and update settings accordingly. + */ + void syncCCDInfo(); + + /** + * @brief Check Focuser and make sure information is updated accordingly. + * @param FocuserNum By default, we check the already selected focuser in the dropdown menu. If FocuserNum is specified, the check is made against this specific focuser in the dropdown menu. + * FocuserNum is the index of the focuser in the dropdown menu. + */ + void checkFocuser(int FocuserNum = -1); + + /** + * @brief Check Filter and make sure information is updated accordingly. + * @param filterNum By default, we check the already selected filter in the dropdown menu. If filterNum is specified, the check is made against this specific filter in the dropdown menu. + * filterNum is the index of the filter in the dropdown menu. + */ + void checkFilter(int filterNum = -1); + + /** + * @brief clearDataPoints Remove all data points from HFR plots + */ + void clearDataPoints(); + + /** + * @brief focusStarSelected The user selected a focus star, save its coordinates and subframe it if subframing is enabled. + * @param x X coordinate + * @param y Y coordinate + */ + void focusStarSelected(int x, int y); + + /** + * @brief newFITS A new FITS blob is received by the CCD driver. + * @param bp pointer to blob data + */ + void newFITS(IBLOB *bp); + + /** + * @brief processFocusNumber Read focus number properties of interest as they arrive from the focuser driver and process them accordingly. + * @param nvp pointer to updated focuser number property. + */ + void processFocusNumber(INumberVectorProperty *nvp); + + /** + * @brief checkFocus Given the minimum required HFR, check focus and calculate HFR. If current HFR exceeds required HFR, start autofocus process, otherwise do nothing. + * @param requiredHFR Minimum HFR to trigger autofocus process. + */ + void checkFocus(double requiredHFR); + + /** + * @brief setFocusStatus Upon completion of the focusing process, set its status (fail or pass) and reset focus process to clean state. + * @param status If true, the focus process finished successfully. Otherwise, it failed. + */ + void setAutoFocusResult(bool status); + + /** + * @brief filterChangeWarning Warn the user it is not a good idea to apply image filter in the filter process as they can skew the HFR calculations. + * @param index Index of image filter selected by the user. + */ + void filterChangeWarning(int index); + + // Log + void appendLogText(const QString &); + + // Adjust focuser offset, relative or absolute + void adjustFocusOffset(int value, bool useAbsoluteOffset); + + // Update Mount module status + void setMountStatus(ISD::Telescope::Status newState); + + /** + * @brief toggleVideo Turn on and off video streaming if supported by the camera. + * @param enabled Set to true to start video streaming, false to stop it if active. + */ + void toggleVideo(bool enabled); + + private slots: + /** + * @brief toggleSubframe Process enabling and disabling subfrag. + * @param enable If true, subframing is enabled. If false, subframing is disabled. Even if subframing is enabled, it must be supported by the CCD driver. + */ + void toggleSubframe(bool enable); + + void checkAutoStarTimeout(); + + void setAbsoluteFocusTicks(); + + void updateBoxSize(int value); + + void processCaptureTimeout(); + + void setCaptureComplete(); + + void showFITSViewer(); + + void toggleFocusingWidgetFullScreen(); + + void setVideoStreamEnabled(bool enabled); + + void syncSettings(); + + signals: + void newLog(const QString &text); + void newStatus(Ekos::FocusState state); + void newHFR(double hfr, int position); + + void absolutePositionChanged(int value); + void focusPositionAdjusted(); + + void suspendGuiding(); + void resumeGuiding(); + void newStarPixmap(QPixmap &); + void newProfilePixmap(QPixmap &); + + private: + + //////////////////////////////////////////////////////////////////// + /// Connections + //////////////////////////////////////////////////////////////////// + void initConnections(); + + //////////////////////////////////////////////////////////////////// + /// Settings + //////////////////////////////////////////////////////////////////// + + /** + * @brief initSettings Connect settings to slots to update the value when changed + */ + void initSettingsConnections(); + /** + * @brief loadSettings Load setting from Options and set them accordingly. + */ + void loadSettings(); + + //////////////////////////////////////////////////////////////////// + /// HFR Plot + //////////////////////////////////////////////////////////////////// + void initPlots(); + void drawHFRPlot(); + void drawProfilePlot(); + + //////////////////////////////////////////////////////////////////// + /// Positions + //////////////////////////////////////////////////////////////////// + void getAbsFocusPosition(); + void autoFocusAbs(); + void autoFocusRel(); + void resetButtons(); + void stop(bool aborted = false); + bool findMinimum(double expected, double *position, double *hfr); + static double fn1(double x, void *params); + + void initView(); + + /** + * @brief syncTrackingBoxPosition Sync the tracking box to the current selected star center + */ + void syncTrackingBoxPosition(); + + /// Focuser device needed for focus operation + ISD::Focuser *currentFocuser { nullptr }; + /// CCD device needed for focus operation + ISD::CCD *currentCCD { nullptr }; + + /// Optional device filter + ISD::GDInterface *currentFilter { nullptr }; + /// Current filter position + int currentFilterPosition { -1 }; + int fallbackFilterPosition { -1 }; + /// True if we need to change filter position and wait for result before continuing capture + bool filterPositionPending { false }; + bool fallbackFilterPending { false }; + + /// List of Focusers + QList Focusers; + /// List of CCDs + QList CCDs; + /// They're generic GDInterface because they could be either ISD::CCD or ISD::Filter + QList Filters; + + /// As the name implies + FocusDirection lastFocusDirection { FOCUS_NONE }; + /// What type of focusing are we doing right now? + FocusType focusType { FOCUS_MANUAL }; + /// Focus HFR & Centeroid algorithms + StarAlgorithm focusDetection { ALGORITHM_GRADIENT }; + /// Focus Process Algorithm + FocusAlgorithm focusAlgorithm { FOCUS_ITERATIVE }; + + /********************* + * HFR Club variables + *********************/ + + /// Current HFR value just fetched from FITS file + double currentHFR { 0 }; + /// Last HFR value recorded + double lastHFR { 0 }; + /// If (currentHFR > deltaHFR) we start the autofocus process. + double minimumRequiredHFR { -1 }; + /// Maximum HFR recorded + double maxHFR { 1 }; + /// Is HFR increasing? We're going away from the sweet spot! If HFRInc=1, we re-capture just to make sure HFR calculations are correct, if HFRInc > 1, we switch directions + int HFRInc { 0 }; + /// If HFR decreasing? Well, good job. Once HFR start decreasing, we can start calculating HFR slope and estimating our next move. + int HFRDec { 0 }; + + /**************************** + * Absolute position focusers + ****************************/ + /// Absolute focus position + double currentPosition { 0 }; + /// What was our position before we started the focus process? + int initialFocuserAbsPosition { -1 }; + /// Pulse duration in ms for relative focusers that only support timers, or the number of ticks in a relative or absolute focuser + int pulseDuration { 1000 }; + /// Does the focuser support absolute motion? + bool canAbsMove { false }; + /// Does the focuser support relative motion? + bool canRelMove { false }; + /// Does the focuser support timer-based motion? + bool canTimerMove { false }; + /// Maximum range of motion for our lovely absolute focuser + double absMotionMax { 0 }; + /// Minimum range of motion for our lovely absolute focuser + double absMotionMin { 0 }; + /// How many iterations have we completed now in our absolute autofocus algorithm? We can't go forever + int absIterations { 0 }; + + /**************************** + * Misc. variables + ****************************/ + + /// Are we in the process of capturing an image? + bool captureInProgress { false }; + // Was the frame modified by us? Better keep track since we need to return it to its previous state once we are done with the focus operation. + //bool frameModified; + /// Was the modified frame subFramed? + bool subFramed { false }; + /// If the autofocus process fails, let's not ruin the capture session probably taking place in the next tab. Instead, we should restart it and try again, but we keep count until we hit MAXIMUM_RESET_ITERATIONS + /// and then we truly give up. + int resetFocusIteration { 0 }; + /// Which filter must we use once the autofocus process kicks in? + int lockedFilterIndex { -1 }; + /// Keep track of what we're doing right now + bool inAutoFocus { false }; + bool inFocusLoop { false }; + bool inSequenceFocus { false }; + bool resetFocus { false }; + /// Did we reverse direction? + bool reverseDir { false }; + /// Did the user or the auto selection process finish selecting our focus star? + bool starSelected { false }; + /// Adjust the focus position to a target value + bool adjustFocus { false }; + // Target frame dimensions + //int fx,fy,fw,fh; + /// If HFR=-1 which means no stars detected, we need to decide how many times should the re-capture process take place before we give up or reverse direction. + int noStarCount { 0 }; + /// Track which upload mode the CCD is set to. If set to UPLOAD_LOCAL, then we need to switch it to UPLOAD_CLIENT in order to do focusing, and then switch it back to UPLOAD_LOCAL + ISD::CCD::UploadMode rememberUploadMode { ISD::CCD::UPLOAD_CLIENT }; + /// Previous binning setting + int activeBin { 0 }; + /// HFR values for captured frames before averages + QVector HFRFrames; + // CCD Exposure Looping + bool rememberCCDExposureLooping = { false }; + + QStringList m_LogText; + ITextVectorProperty *filterName { nullptr }; + INumberVectorProperty *filterSlot { nullptr }; + + /**************************** + * Plot variables + ****************************/ + + /// Plot minimum positions + double minPos { 1e6 }; + /// Plot maximum positions + double maxPos { 0 }; + /// List of V curve plot points + /// V-Curve graph + QCPGraph *v_graph { nullptr }; + + // Last gaussian fit values + QVector lastGausIndexes; + QVector lastGausFrequencies; + QCPGraph *currentGaus { nullptr }; + QCPGraph *firstGaus { nullptr }; + QCPGraph *lastGaus { nullptr }; + + QVector hfr_position, hfr_value; + + // Pixmaps + QPixmap profilePixmap; + + /// State + Ekos::FocusState state { Ekos::FOCUS_IDLE }; + + /// FITS Scale + FITSScale defaultScale; + + /// CCD Chip frame settings + QMap frameSettings; + + /// Selected star coordinates + QVector3D starCenter; + + // Remember last star center coordinates in case of timeout in manual select mode + QVector3D rememberStarCenter; + + /// Focus Frame + FITSView *focusView { nullptr }; + + /// Star Select Timer + QTimer waitStarSelectTimer; + + /// FITS Viewer in case user want to display in it instead of internal view + QPointer fv; + + /// Track star position and HFR to know if we're detecting bogus stars due to detection algorithm false positive results + QVector starsHFR; + + /// Relative Profile + QCustomPlot *profilePlot { nullptr }; + QDialog *profileDialog { nullptr }; + + /// Polynomial fitting coefficients + std::vector coeff; + int polySolutionFound { 0 }; + + // Capture timeout timer + QTimer captureTimeout; + uint8_t captureTimeoutCounter { 0 }; + + // Guide Suspend + bool m_GuidingSuspended { false }; + + // Filter Manager + QSharedPointer filterManager; }; } diff --git a/kstars/ekos/focus/focus.cpp b/kstars/ekos/focus/focus.cpp --- a/kstars/ekos/focus/focus.cpp +++ b/kstars/ekos/focus/focus.cpp @@ -42,272 +42,47 @@ { Focus::Focus() { + // #1 Set the UI setupUi(this); + // #2 Register DBus qRegisterMetaType("Ekos::FocusState"); qDBusRegisterMetaType(); - new FocusAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Focus", this); - //frameModified = false; - - waitStarSelectTimer.setInterval(AUTO_STAR_TIMEOUT); - connect(&waitStarSelectTimer, &QTimer::timeout, this, &Ekos::Focus::checkAutoStarTimeout); - - connect(liveVideoB, &QPushButton::clicked, this, &Ekos::Focus::toggleVideo); - - //fy=fw=fh=0; - HFRFrames.clear(); - - FilterDevicesCombo->addItem("--"); - - showFITSViewerB->setIcon( - QIcon::fromTheme("kstars_fitsviewer")); - showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); - connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Focus::showFITSViewer); - - toggleFullScreenB->setIcon( - QIcon::fromTheme("view-fullscreen")); - toggleFullScreenB->setShortcut(Qt::Key_F4); - toggleFullScreenB->setAttribute(Qt::WA_LayoutUsesWidgetRect); - connect(toggleFullScreenB, &QPushButton::clicked, this, &Ekos::Focus::toggleFocusingWidgetFullScreen); - - // Exposure Timeout - captureTimeout.setSingleShot(true); - connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Focus::processCaptureTimeout); - - connect(startFocusB, &QPushButton::clicked, this, &Ekos::Focus::start); - connect(stopFocusB, &QPushButton::clicked, this, &Ekos::Focus::checkStopFocus); - - connect(focusOutB, &QPushButton::clicked, [&]() - { - focusOut(); - }); - connect(focusInB, &QPushButton::clicked, [&]() - { - focusIn(); - }); - - connect(captureB, &QPushButton::clicked, this, &Ekos::Focus::capture); - - connect(startLoopB, &QPushButton::clicked, this, &Ekos::Focus::startFraming); - - connect(useSubFrame, &QCheckBox::toggled, this, &Ekos::Focus::toggleSubframe); - - connect(resetFrameB, &QPushButton::clicked, this, &Ekos::Focus::resetFrame); - - connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::setDefaultCCD); - connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::checkCCD); - - connect(useFullField, &QCheckBox::toggled, [&](bool toggled) - { - Options::setFocusUseFullField(toggled); - fullFieldInnerRing->setEnabled(toggled); - fullFieldOuterRing->setEnabled(toggled); - if (toggled) - { - useSubFrame->setChecked(false); - useAutoStar->setChecked(false); - } - else - { - // Disable the overlay - focusView->setStarFilterRange(0, 1); - } - }); - - connect(fullFieldInnerRing, static_cast(&QDoubleSpinBox::valueChanged), - [ = ](double d) - { - Options::setFocusFullFieldInnerRadius(d); - }); - connect(fullFieldOuterRing, static_cast(&QDoubleSpinBox::valueChanged), - [ = ](double d) - { - Options::setFocusFullFieldOuterRadius(d); - }); - - FocusSettleTime->setValue(Options::focusSettleTime()); - connect(FocusSettleTime, static_cast(&QDoubleSpinBox::valueChanged), - [ = ](double d) - { - Options::setFocusSettleTime(d); - }); - - GuideSettleTime->setValue(Options::guideSettleTime()); - connect(GuideSettleTime, static_cast(&QDoubleSpinBox::valueChanged), - [ = ](double d) - { - Options::setGuideSettleTime(d); - }); - - connect(focuserCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::setDefaultFocuser); - connect(focuserCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::checkFocuser); - - connect(FilterDevicesCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::checkFilter); - connect(setAbsTicksB, &QPushButton::clicked, this, &Ekos::Focus::setAbsoluteFocusTicks); - connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::setActiveBinning); - connect(focusBoxSize, static_cast(&QSpinBox::valueChanged), this, &Ekos::Focus::updateBoxSize); - - focusDetection = static_cast(Options::focusDetection()); - focusDetectionCombo->setCurrentIndex(focusDetection); - - connect(focusDetectionCombo, static_cast(&QComboBox::activated), this, [&](int index) - { - focusDetection = static_cast(index); - thresholdSpin->setEnabled(focusDetection == ALGORITHM_THRESHOLD); - Options::setFocusDetection(index); - }); - - focusAlgorithm = static_cast(Options::focusAlgorithm()); - focusAlgorithmCombo->setCurrentIndex(focusAlgorithm); - connect(focusAlgorithmCombo, static_cast(&QComboBox::activated), this, [&](int index) - { - focusAlgorithm = static_cast(index); - //toleranceIN->setEnabled(focusAlgorithm == FOCUS_ITERATIVE); - Options::setFocusAlgorithm(index); - }); - - activeBin = Options::focusXBin(); - binningCombo->setCurrentIndex(activeBin - 1); - - focusFramesSpin->setValue(Options::focusFramesCount()); - - connect(clearDataB, &QPushButton::clicked, this, &Ekos::Focus::clearDataPoints); - - profileDialog = new QDialog(this); - profileDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - QVBoxLayout *profileLayout = new QVBoxLayout(profileDialog); - profileDialog->setWindowTitle(i18n("Relative Profile")); - profilePlot = new QCustomPlot(profileDialog); - profilePlot->setBackground(QBrush(Qt::black)); - profilePlot->xAxis->setBasePen(QPen(Qt::white, 1)); - profilePlot->yAxis->setBasePen(QPen(Qt::white, 1)); - profilePlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); - profilePlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); - profilePlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); - profilePlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); - profilePlot->xAxis->grid()->setZeroLinePen(Qt::NoPen); - profilePlot->yAxis->grid()->setZeroLinePen(Qt::NoPen); - profilePlot->xAxis->setBasePen(QPen(Qt::white, 1)); - profilePlot->yAxis->setBasePen(QPen(Qt::white, 1)); - profilePlot->xAxis->setTickPen(QPen(Qt::white, 1)); - profilePlot->yAxis->setTickPen(QPen(Qt::white, 1)); - profilePlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); - profilePlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); - profilePlot->xAxis->setTickLabelColor(Qt::white); - profilePlot->yAxis->setTickLabelColor(Qt::white); - profilePlot->xAxis->setLabelColor(Qt::white); - profilePlot->yAxis->setLabelColor(Qt::white); - - profileLayout->addWidget(profilePlot); - profileDialog->setLayout(profileLayout); - profileDialog->resize(400, 300); + // #3 Init connections + initConnections(); - connect(relativeProfileB, &QPushButton::clicked, profileDialog, &QDialog::show); - - currentGaus = profilePlot->addGraph(); - currentGaus->setLineStyle(QCPGraph::lsLine); - currentGaus->setPen(QPen(Qt::red, 2)); + // #4 Init Plots + initPlots(); - lastGaus = profilePlot->addGraph(); - lastGaus->setLineStyle(QCPGraph::lsLine); - QPen pen(Qt::darkGreen); - pen.setStyle(Qt::DashLine); - pen.setWidth(2); - lastGaus->setPen(pen); - - HFRPlot->setBackground(QBrush(Qt::black)); - - HFRPlot->xAxis->setBasePen(QPen(Qt::white, 1)); - HFRPlot->yAxis->setBasePen(QPen(Qt::white, 1)); - - HFRPlot->xAxis->setTickPen(QPen(Qt::white, 1)); - HFRPlot->yAxis->setTickPen(QPen(Qt::white, 1)); - - HFRPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); - HFRPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); - - HFRPlot->xAxis->setTickLabelColor(Qt::white); - HFRPlot->yAxis->setTickLabelColor(Qt::white); - - HFRPlot->xAxis->setLabelColor(Qt::white); - HFRPlot->yAxis->setLabelColor(Qt::white); - - HFRPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); - HFRPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); - HFRPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); - HFRPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); - HFRPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen); - HFRPlot->yAxis->grid()->setZeroLinePen(Qt::NoPen); - - HFRPlot->yAxis->setLabel(i18n("HFR")); - - HFRPlot->setInteractions(QCP::iRangeZoom); - HFRPlot->setInteraction(QCP::iRangeDrag, true); - - v_graph = HFRPlot->addGraph(); - v_graph->setLineStyle(QCPGraph::lsNone); - v_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::white, Qt::red, 3)); + // #5 Init View + initView(); + // #6 Reset all buttons to default states resetButtons(); - appendLogText(i18n("Idle.")); - + // #7 Image Effects for (auto &filter : FITSViewer::filterTypes) filterCombo->addItem(filter); - filterCombo->setCurrentIndex(Options::focusEffect()); defaultScale = static_cast(Options::focusEffect()); connect(filterCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::filterChangeWarning); - exposureIN->setValue(Options::focusExposure()); - toleranceIN->setValue(Options::focusTolerance()); - stepIN->setValue(Options::focusTicks()); - useAutoStar->setChecked(Options::focusAutoStarEnabled()); - focusBoxSize->setValue(Options::focusBoxSize()); - if (Options::focusMaxTravel() > maxTravelIN->maximum()) - maxTravelIN->setMaximum(Options::focusMaxTravel()); - maxTravelIN->setValue(Options::focusMaxTravel()); - useSubFrame->setChecked(Options::focusSubFrame()); - suspendGuideCheck->setChecked(Options::suspendGuiding()); - darkFrameCheck->setChecked(Options::useFocusDarkFrame()); - thresholdSpin->setValue(Options::focusThreshold()); - useFullField->setChecked(Options::focusUseFullField()); - fullFieldInnerRing->setValue(Options::focusFullFieldInnerRadius()); - fullFieldOuterRing->setValue(Options::focusFullFieldOuterRadius()); - //focusFramesSpin->setValue(Options::focusFrames()); - - connect(thresholdSpin, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::setThreshold); - - focusView = new FITSView(focusingWidget, FITS_FOCUS); - focusView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - focusView->setBaseSize(focusingWidget->size()); - focusView->createFloatingToolBar(); - QVBoxLayout *vlayout = new QVBoxLayout(); - vlayout->addWidget(focusView); - focusingWidget->setLayout(vlayout); - connect(focusView, &FITSView::trackingStarSelected, this, &Ekos::Focus::focusStarSelected, Qt::UniqueConnection); - - focusView->setStarsEnabled(true); + // #8 Load All settings + loadSettings(); - // Reset star center on auto star check toggle - connect(useAutoStar, &QCheckBox::toggled, this, [&](bool enabled) - { - if (enabled) - { - starCenter = QVector3D(); - starSelected = false; - focusView->setTrackingBox(QRect()); - } - }); + // #9 Init Setting Connection now + initSettingsConnections(); //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); + + appendLogText(i18n("Idle.")); } Focus::~Focus() @@ -363,16 +138,6 @@ return false; } -void Focus::setDefaultCCD(QString ccd) -{ - Options::setDefaultFocusCCD(ccd); -} - -void Focus::setDefaultFocuser(QString focuser) -{ - Options::setDefaultFocusFocuser(focuser); -} - QString Focus::camera() { if (currentCCD) @@ -421,6 +186,7 @@ for (int i = 1; i <= subBinX; i++) binningCombo->addItem(QString("%1x%2").arg(i).arg(i)); + activeBin = Options::focusXBin(); binningCombo->setCurrentIndex(activeBin - 1); } else @@ -532,6 +298,9 @@ checkFilter(1); FilterDevicesCombo->setCurrentIndex(1); + + if (Options::defaultFocusFilterWheel().isEmpty() == false) + FilterDevicesCombo->setCurrentText(Options::defaultFocusFilterWheel()); } bool Focus::setFilterWheel(const QString &device) @@ -597,6 +366,8 @@ if (filterNum <= Filters.count()) currentFilter = Filters.at(filterNum - 1); + //Options::setDefaultFocusFilterWheel(currentFilter->getDeviceName()); + filterManager->setCurrentFilterWheel(currentFilter); FilterPosCombo->clear(); @@ -607,6 +378,8 @@ FilterPosCombo->setCurrentIndex(currentFilterPosition - 1); + //Options::setDefaultFocusFilterWheelFilter(FilterPosCombo->currentText()); + exposureIN->setValue(filterManager->getFilterExposure()); } @@ -815,17 +588,17 @@ firstGaus = nullptr; } - Options::setFocusTicks(stepIN->value()); - Options::setFocusTolerance(toleranceIN->value()); - //Options::setFocusExposure(exposureIN->value()); - Options::setFocusMaxTravel(maxTravelIN->value()); - Options::setFocusBoxSize(focusBoxSize->value()); - Options::setFocusSubFrame(useSubFrame->isChecked()); - Options::setFocusAutoStarEnabled(useAutoStar->isChecked()); - Options::setSuspendGuiding(suspendGuideCheck->isChecked()); - Options::setUseFocusDarkFrame(darkFrameCheck->isChecked()); - Options::setFocusFramesCount(focusFramesSpin->value()); - Options::setFocusUseFullField(useFullField->isChecked()); + // Options::setFocusTicks(stepIN->value()); + // Options::setFocusTolerance(toleranceIN->value()); + // //Options::setFocusExposure(exposureIN->value()); + // Options::setFocusMaxTravel(maxTravelIN->value()); + // Options::setFocusBoxSize(focusBoxSize->value()); + // Options::setFocusSubFrame(useSubFrame->isChecked()); + // Options::setFocusAutoStarEnabled(useAutoStar->isChecked()); + // Options::setSuspendGuiding(suspendGuideCheck->isChecked()); + // Options::setUseFocusDarkFrame(darkFrameCheck->isChecked()); + // Options::setFocusFramesCount(focusFramesSpin->value()); + // Options::setFocusUseFullField(useFullField->isChecked()); qCDebug(KSTARS_EKOS_FOCUS) << "Starting focus with box size: " << focusBoxSize->value() << " Subframe: " << ( useSubFrame->isChecked() ? "yes" : "no" ) @@ -1317,7 +1090,7 @@ const auto median = ((HFRFrames.size() % 2) ? HFRFrames[HFRFrames.size() / 2] : - ((double)HFRFrames[HFRFrames.size() / 2 - 1] + HFRFrames[HFRFrames.size() / 2]) * .5); + (static_cast(HFRFrames[HFRFrames.size() / 2 - 1]) + HFRFrames[HFRFrames.size() / 2]) * .5); const auto mean = std::accumulate(HFRFrames.begin(), HFRFrames.end(), .0) / HFRFrames.size(); double variance = 0; foreach (auto val, HFRFrames) @@ -2216,10 +1989,7 @@ setAutoFocusResult(false); } } - break; - - default: - break; + break; } } @@ -2582,7 +2352,7 @@ //starRect = QRect(x-focusBoxSize->value()/(subBinX*2), y-focusBoxSize->value()/(subBinY*2), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY); double dist = sqrt((starCenter.x() - x) * (starCenter.x() - x) + (starCenter.y() - y) * (starCenter.y() - y)); - squareMovedOutside = (dist > ((double)focusBoxSize->value() / subBinX)); + squareMovedOutside = (dist > (static_cast(focusBoxSize->value()) / subBinX)); starCenter.setX(x); starCenter.setY(y); //starRect = QRect( starCenter.x()-focusBoxSize->value()/(2*subBinX), starCenter.y()-focusBoxSize->value()/(2*subBinY), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY); @@ -2802,16 +2572,11 @@ currentFocuser->moveAbs(absTicksSpin->value()); } -void Focus::setActiveBinning(int bin) -{ - activeBin = bin + 1; - Options::setFocusXBin(activeBin); -} - -void Focus::setThreshold(double value) -{ - Options::setFocusThreshold(value); -} +//void Focus::setActiveBinning(int bin) +//{ +// activeBin = bin + 1; +// Options::setFocusXBin(activeBin); +//} // TODO remove from kstars.kcfg /*void Focus::setFrames(int value) @@ -3084,21 +2849,21 @@ { if (currentFilter) filterManager->setFilterExposure(FilterPosCombo->currentIndex(), exposureIN->value()); - else - Options::setFocusExposure(exposureIN->value()); }); connect(filterManager.data(), &FilterManager::labelsChanged, this, [this]() { FilterPosCombo->clear(); FilterPosCombo->addItems(filterManager->getFilterLabels()); currentFilterPosition = filterManager->getFilterPosition(); FilterPosCombo->setCurrentIndex(currentFilterPosition - 1); + //Options::setDefaultFocusFilterWheelFilter(FilterPosCombo->currentText()); }); connect(filterManager.data(), &FilterManager::positionChanged, this, [this]() { currentFilterPosition = filterManager->getFilterPosition(); FilterPosCombo->setCurrentIndex(currentFilterPosition - 1); + //Options::setDefaultFocusFilterWheelFilter(FilterPosCombo->currentText()); }); connect(filterManager.data(), &FilterManager::exposureChanged, this, [this]() { @@ -3110,8 +2875,8 @@ [ = ](const QString & text) { exposureIN->setValue(filterManager->getFilterExposure(text)); - } - ); + //Options::setDefaultFocusFilterWheelFilter(text); + }); } void Focus::toggleVideo(bool enabled) @@ -3172,4 +2937,390 @@ captureTimeout.start(exposureIN->value() * 1000 + FOCUS_TIMEOUT_THRESHOLD); } +void Focus::syncSettings() +{ + QDoubleSpinBox *dsb = nullptr; + QSpinBox *sb = nullptr; + QCheckBox *cb = nullptr; + QComboBox *cbox = nullptr; + + if ( (dsb = qobject_cast(sender()))) + { + /////////////////////////////////////////////////////////////////////////// + /// Focuser Group + /////////////////////////////////////////////////////////////////////////// + if (dsb == FocusSettleTime) + Options::setFocusSettleTime(dsb->value()); + + /////////////////////////////////////////////////////////////////////////// + /// CCD & Filter Wheel Group + /////////////////////////////////////////////////////////////////////////// + else if (dsb == gainIN) + Options::setFocusGain(dsb->value()); + + /////////////////////////////////////////////////////////////////////////// + /// Settings Group + /////////////////////////////////////////////////////////////////////////// + else if (dsb == fullFieldInnerRing) + Options::setFocusFullFieldInnerRadius(dsb->value()); + else if (dsb == fullFieldOuterRing) + Options::setFocusFullFieldOuterRadius(dsb->value()); + else if (dsb == GuideSettleTime) + Options::setGuideSettleTime(dsb->value()); + else if (dsb == maxTravelIN) + Options::setFocusMaxTravel(dsb->value()); + else if (dsb == toleranceIN) + Options::setFocusTolerance(dsb->value()); + else if (dsb == thresholdSpin) + Options::setFocusThreshold(dsb->value()); + } + else if ( (sb = qobject_cast(sender()))) + { + /////////////////////////////////////////////////////////////////////////// + /// Settings Group + /////////////////////////////////////////////////////////////////////////// + if (sb == focusBoxSize) + Options::setFocusBoxSize(sb->value()); + else if (sb == stepIN) + Options::setFocusTicks(sb->value()); + else if (sb == focusFramesSpin) + Options::setFocusFramesCount(sb->value()); + } + else if ( (cb = qobject_cast(sender()))) + { + /////////////////////////////////////////////////////////////////////////// + /// Settings Group + /////////////////////////////////////////////////////////////////////////// + if (cb == useAutoStar) + Options::setFocusAutoStarEnabled(cb->isChecked()); + else if (cb == useSubFrame) + Options::setFocusSubFrame(cb->isChecked()); + else if (cb == darkFrameCheck) + Options::setUseFocusDarkFrame(cb->isChecked()); + else if (cb == useFullField) + Options::setFocusUseFullField(cb->isChecked()); + else if (cb == suspendGuideCheck) + Options::setSuspendGuiding(cb->isChecked()); + } + else if ( (cbox = qobject_cast(sender()))) + { + /////////////////////////////////////////////////////////////////////////// + /// CCD & Filter Wheel Group + /////////////////////////////////////////////////////////////////////////// + if (cbox == focuserCombo) + Options::setDefaultFocusFocuser(cbox->currentText()); + else if (cbox == CCDCaptureCombo) + Options::setDefaultFocusCCD(cbox->currentText()); + else if (cbox == binningCombo) + { + activeBin = cbox->currentIndex() + 1; + Options::setFocusXBin(activeBin); + } + else if (cbox == FilterDevicesCombo) + Options::setDefaultFocusFilterWheel(cbox->currentText()); + // Filter Effects already taken care of in filterChangeWarning + + /////////////////////////////////////////////////////////////////////////// + /// Settings Group + /////////////////////////////////////////////////////////////////////////// + else if (cbox == focusAlgorithmCombo) + Options::setFocusAlgorithm(cbox->currentIndex()); + else if (cbox == focusDetectionCombo) + Options::setFocusDetection(cbox->currentIndex()); + } +} + +void Focus::loadSettings() +{ + /////////////////////////////////////////////////////////////////////////// + /// Focuser Group + /////////////////////////////////////////////////////////////////////////// + // Focus settle time + FocusSettleTime->setValue(Options::focusSettleTime()); + + /////////////////////////////////////////////////////////////////////////// + /// CCD & Filter Wheel Group + /////////////////////////////////////////////////////////////////////////// + // Binning + activeBin = Options::focusXBin(); + binningCombo->setCurrentIndex(activeBin - 1); + // Gain + gainIN->setValue(Options::focusGain()); + + /////////////////////////////////////////////////////////////////////////// + /// Settings Group + /////////////////////////////////////////////////////////////////////////// + // Auto Star? + useAutoStar->setChecked(Options::focusAutoStarEnabled()); + // Subframe? + useSubFrame->setChecked(Options::focusSubFrame()); + // Dark frame? + darkFrameCheck->setChecked(Options::useFocusDarkFrame()); + // Use full field? + useFullField->setChecked(Options::focusUseFullField()); + // full field inner ring + fullFieldInnerRing->setValue(Options::focusFullFieldInnerRadius()); + // full field outer ring + fullFieldOuterRing->setValue(Options::focusFullFieldOuterRadius()); + // Suspend guiding? + suspendGuideCheck->setChecked(Options::suspendGuiding()); + // Guide Setting time + GuideSettleTime->setValue(Options::guideSettleTime()); + + // Box Size + focusBoxSize->setValue(Options::focusBoxSize()); + // Max Travel + if (Options::focusMaxTravel() > maxTravelIN->maximum()) + maxTravelIN->setMaximum(Options::focusMaxTravel()); + maxTravelIN->setValue(Options::focusMaxTravel()); + // Step + stepIN->setValue(Options::focusTicks()); + // Tolernace + toleranceIN->setValue(Options::focusTolerance()); + // Threshold spin + thresholdSpin->setValue(Options::focusThreshold()); + // Focus Algorithm + focusAlgorithm = static_cast(Options::focusAlgorithm()); + focusAlgorithmCombo->setCurrentIndex(focusAlgorithm); + // Frames Count + focusFramesSpin->setValue(Options::focusFramesCount()); + // Focus Detection + focusDetection = static_cast(Options::focusDetection()); + thresholdSpin->setEnabled(focusDetection == ALGORITHM_THRESHOLD); + focusDetectionCombo->setCurrentIndex(focusDetection); +} + +void Focus::initSettingsConnections() +{ + /////////////////////////////////////////////////////////////////////////// + /// Focuser Group + /////////////////////////////////////////////////////////////////////////// + connect(focuserCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); + connect(FocusSettleTime, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + + /////////////////////////////////////////////////////////////////////////// + /// CCD & Filter Wheel Group + /////////////////////////////////////////////////////////////////////////// + connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); + connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); + connect(gainIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + connect(FilterDevicesCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); + connect(FilterPosCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); + + /////////////////////////////////////////////////////////////////////////// + /// Settings Group + /////////////////////////////////////////////////////////////////////////// + connect(useAutoStar, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); + connect(useSubFrame, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); + connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); + connect(useFullField, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); + connect(fullFieldInnerRing, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + connect(fullFieldOuterRing, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + connect(suspendGuideCheck, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); + connect(GuideSettleTime, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + + connect(focusBoxSize, static_cast(&QSpinBox::valueChanged), this, &Focus::syncSettings); + connect(maxTravelIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + connect(stepIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + connect(toleranceIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + connect(thresholdSpin, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings); + + connect(focusAlgorithmCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); + connect(focusFramesSpin, static_cast(&QSpinBox::valueChanged), this, &Focus::syncSettings); + connect(focusDetectionCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::syncSettings); +} + +void Focus::initPlots() +{ + connect(clearDataB, &QPushButton::clicked, this, &Ekos::Focus::clearDataPoints); + + profileDialog = new QDialog(this); + profileDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + QVBoxLayout *profileLayout = new QVBoxLayout(profileDialog); + profileDialog->setWindowTitle(i18n("Relative Profile")); + profilePlot = new QCustomPlot(profileDialog); + profilePlot->setBackground(QBrush(Qt::black)); + profilePlot->xAxis->setBasePen(QPen(Qt::white, 1)); + profilePlot->yAxis->setBasePen(QPen(Qt::white, 1)); + profilePlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + profilePlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + profilePlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + profilePlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + profilePlot->xAxis->grid()->setZeroLinePen(Qt::NoPen); + profilePlot->yAxis->grid()->setZeroLinePen(Qt::NoPen); + profilePlot->xAxis->setBasePen(QPen(Qt::white, 1)); + profilePlot->yAxis->setBasePen(QPen(Qt::white, 1)); + profilePlot->xAxis->setTickPen(QPen(Qt::white, 1)); + profilePlot->yAxis->setTickPen(QPen(Qt::white, 1)); + profilePlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); + profilePlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); + profilePlot->xAxis->setTickLabelColor(Qt::white); + profilePlot->yAxis->setTickLabelColor(Qt::white); + profilePlot->xAxis->setLabelColor(Qt::white); + profilePlot->yAxis->setLabelColor(Qt::white); + + profileLayout->addWidget(profilePlot); + profileDialog->setLayout(profileLayout); + profileDialog->resize(400, 300); + + connect(relativeProfileB, &QPushButton::clicked, profileDialog, &QDialog::show); + + currentGaus = profilePlot->addGraph(); + currentGaus->setLineStyle(QCPGraph::lsLine); + currentGaus->setPen(QPen(Qt::red, 2)); + + lastGaus = profilePlot->addGraph(); + lastGaus->setLineStyle(QCPGraph::lsLine); + QPen pen(Qt::darkGreen); + pen.setStyle(Qt::DashLine); + pen.setWidth(2); + lastGaus->setPen(pen); + + HFRPlot->setBackground(QBrush(Qt::black)); + + HFRPlot->xAxis->setBasePen(QPen(Qt::white, 1)); + HFRPlot->yAxis->setBasePen(QPen(Qt::white, 1)); + + HFRPlot->xAxis->setTickPen(QPen(Qt::white, 1)); + HFRPlot->yAxis->setTickPen(QPen(Qt::white, 1)); + + HFRPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); + HFRPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); + + HFRPlot->xAxis->setTickLabelColor(Qt::white); + HFRPlot->yAxis->setTickLabelColor(Qt::white); + + HFRPlot->xAxis->setLabelColor(Qt::white); + HFRPlot->yAxis->setLabelColor(Qt::white); + + HFRPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + HFRPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + HFRPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + HFRPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + HFRPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen); + HFRPlot->yAxis->grid()->setZeroLinePen(Qt::NoPen); + + HFRPlot->yAxis->setLabel(i18n("HFR")); + + HFRPlot->setInteractions(QCP::iRangeZoom); + HFRPlot->setInteraction(QCP::iRangeDrag, true); + + v_graph = HFRPlot->addGraph(); + v_graph->setLineStyle(QCPGraph::lsNone); + v_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::white, Qt::red, 3)); + +} + +void Focus::initConnections() +{ + // How long do we wait until the user select a star? + waitStarSelectTimer.setInterval(AUTO_STAR_TIMEOUT); + connect(&waitStarSelectTimer, &QTimer::timeout, this, &Ekos::Focus::checkAutoStarTimeout); + connect(liveVideoB, &QPushButton::clicked, this, &Ekos::Focus::toggleVideo); + + // Show FITS Image in a new window + showFITSViewerB->setIcon(QIcon::fromTheme("kstars_fitsviewer")); + showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); + connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Focus::showFITSViewer); + + // Toggle FITS View to full screen + toggleFullScreenB->setIcon(QIcon::fromTheme("view-fullscreen")); + toggleFullScreenB->setShortcut(Qt::Key_F4); + toggleFullScreenB->setAttribute(Qt::WA_LayoutUsesWidgetRect); + connect(toggleFullScreenB, &QPushButton::clicked, this, &Ekos::Focus::toggleFocusingWidgetFullScreen); + + // How long do we wait until an exposure times out and needs a retry? + captureTimeout.setSingleShot(true); + connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Focus::processCaptureTimeout); + + // Start/Stop focus + connect(startFocusB, &QPushButton::clicked, this, &Ekos::Focus::start); + connect(stopFocusB, &QPushButton::clicked, this, &Ekos::Focus::checkStopFocus); + + // Focus IN/OUT + connect(focusOutB, &QPushButton::clicked, [&]() + { + focusOut(); + }); + connect(focusInB, &QPushButton::clicked, [&]() + { + focusIn(); + }); + + // Capture a single frame + connect(captureB, &QPushButton::clicked, this, &Ekos::Focus::capture); + // Start continious capture + connect(startLoopB, &QPushButton::clicked, this, &Ekos::Focus::startFraming); + // Use a subframe when capturing + connect(useSubFrame, &QCheckBox::toggled, this, &Ekos::Focus::toggleSubframe); + // Reset frame dimensions to default + connect(resetFrameB, &QPushButton::clicked, this, &Ekos::Focus::resetFrame); + // Sync setting if full field setting is toggled. + connect(useFullField, &QCheckBox::toggled, [&](bool toggled) + { + fullFieldInnerRing->setEnabled(toggled); + fullFieldOuterRing->setEnabled(toggled); + if (toggled) + { + useSubFrame->setChecked(false); + useAutoStar->setChecked(false); + } + else + { + // Disable the overlay + focusView->setStarFilterRange(0, 1); + } + }); + + + // Sync settings if the CCD selection is updated. + connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::checkCCD); + // Sync settings if the Focuser selection is updated. + connect(focuserCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::checkFocuser); + // Sync settings if the filter selection is updated. + connect(FilterDevicesCombo, static_cast(&QComboBox::activated), this, &Ekos::Focus::checkFilter); + + // Set focuser absolute position + connect(setAbsTicksB, &QPushButton::clicked, this, &Ekos::Focus::setAbsoluteFocusTicks); + // Update the focuser box size used to enclose a star + connect(focusBoxSize, static_cast(&QSpinBox::valueChanged), this, &Ekos::Focus::updateBoxSize); + + // Update the focuser star detection if the detection algorithm selection changes. + connect(focusDetectionCombo, static_cast(&QComboBox::activated), this, [&](int index) + { + focusDetection = static_cast(index); + thresholdSpin->setEnabled(focusDetection == ALGORITHM_THRESHOLD); + }); + + // Update the focuser solution algorithm if the selection changes. + connect(focusAlgorithmCombo, static_cast(&QComboBox::activated), this, [&](int index) + { + focusAlgorithm = static_cast(index); + }); + + // Reset star center on auto star check toggle + connect(useAutoStar, &QCheckBox::toggled, this, [&](bool enabled) + { + if (enabled) + { + starCenter = QVector3D(); + starSelected = false; + focusView->setTrackingBox(QRect()); + } + }); +} + +void Focus::initView() +{ + focusView = new FITSView(focusingWidget, FITS_FOCUS); + focusView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + focusView->setBaseSize(focusingWidget->size()); + focusView->createFloatingToolBar(); + QVBoxLayout *vlayout = new QVBoxLayout(); + vlayout->addWidget(focusView); + focusingWidget->setLayout(vlayout); + connect(focusView, &FITSView::trackingStarSelected, this, &Ekos::Focus::focusStarSelected, Qt::UniqueConnection); + focusView->setStarsEnabled(true); +} + } diff --git a/kstars/ekos/focus/focus.ui b/kstars/ekos/focus/focus.ui --- a/kstars/ekos/focus/focus.ui +++ b/kstars/ekos/focus/focus.ui @@ -6,8 +6,8 @@ 0 0 - 674 - 488 + 697 + 526 @@ -431,6 +431,11 @@ false + + + -- + + diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h --- a/kstars/ekos/guide/guide.h +++ b/kstars/ekos/guide/guide.h @@ -446,7 +446,7 @@ void onInfoRateChanged(double val); void onEnableDirRA(bool enable); void onEnableDirDEC(bool enable); - void onInputParamChanged(); + void syncSettings(); //void onRapidGuideChanged(bool enable); @@ -528,6 +528,11 @@ bool executeOperationStack(); bool executeOneOperation(GuideState operation); + // Init Functions + void initPlots(); + void initView(); + void initConnections(); + bool captureOneFrame(); // Operation Stack @@ -552,20 +557,20 @@ QVector3D starCenter; // Guide Params - double ccdPixelSizeX { 0 }; - double ccdPixelSizeY { 0 }; - double aperture { 0 }; - double focal_length { 0 }; + double ccdPixelSizeX { -1 }; + double ccdPixelSizeY { -1 }; + double aperture { -1 }; + double focal_length { -1 }; double guideDeviationRA { 0 }; double guideDeviationDEC { 0 }; - double pixScaleX { 0 }; - double pixScaleY { 0 }; + double pixScaleX { -1 }; + double pixScaleY { -1 }; // Rapid Guide //bool rapidGuideReticleSet; // State - GuideState state; + GuideState state { GUIDE_IDLE }; // Guide timer QTime guideTimer; diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp --- a/kstars/ekos/guide/guide.cpp +++ b/kstars/ekos/guide/guide.cpp @@ -37,53 +37,31 @@ { 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); - // Devices - currentCCD = nullptr; - currentTelescope = nullptr; - guider = nullptr; - - // AO Driver - AODriver = nullptr; + // #3 Init Plots + initPlots(); - // ST4 Driver - GuideDriver = nullptr; + // #4 Init View + initView(); - // Subframe - subFramed = false; - - // To do calibrate + guide in one command - //autoCalibrateGuide = false; + // #5 Load all settings + loadSettings(); - connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither); + // #6 Init Connections + initConnections(); - 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); - ccdPixelSizeX = ccdPixelSizeY = aperture = focal_length = pixScaleX = pixScaleY = -1; - guideDeviationRA = guideDeviationDEC = 0; - useGuideHead = false; - //rapidGuideReticleSet = false; - // Guiding Rate - Advisory only - connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); - // Load all settings - loadSettings(); // Image Filters for (auto &filter : FITSViewer::filterTypes) @@ -120,3221 +98,3285 @@ exposureIN->setRecommendedValues(exposureValues); connect(exposureIN, &NonLinearDoubleSpinBox::editingFinished, this, &Ekos::Guide::saveDefaultGuideExposure); - // 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); - } - else if (index >= 0) - { - // Disable or enable selected CCD based on options - QString ccdName = guiderCombo->currentText().remove(" Guider"); - setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); - 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 - connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); + // 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(); - // ST4 Selection - connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString &text) + connect(opsGuide, &OpsGuide::settingsUpdated, [this]() { - setDefaultST4(text); - setST4(text); + onThresholdChanged(Options::guideAlgorithm()); }); - // Binning Combo Selection - connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateCCDBin); + page = dialog->addPage(opsGuide, i18n("Guide")); + page->setIcon(QIcon::fromTheme("kstars_guides")); - // RA/DEC Enable directions - connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); - connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); + internalGuider->setGuideView(guideView); - // 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); + // Set current guide type + setGuiderType(-1); - // Declination Swap - connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); + //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); +} - // PID Control - Proportional Gain - connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +Guide::~Guide() +{ + delete guider; +} - // PID Control - Integral Gain - connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +void Guide::handleHorizontalPlotSizeChange() +{ + driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); + driftPlot->replot(); +} - // PID Control - Derivative Gain - connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +void Guide::handleVerticalPlotSizeChange() +{ + driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); + driftPlot->replot(); +} - // Max Pulse Duration (ms) - connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +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); + } +} - // Min Pulse Duration (ms) - connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +void Guide::buildTarget() +{ + double accuracyRadius = accuracyRadiusSpin->value(); + Options::setGuiderAccuracyThreshold(accuracyRadius); - // Capture - connect(captureB, &QPushButton::clicked, this, [this]() + if (centralTarget) { - state = GUIDE_CAPTURE; - emit newStatus(state); - - capture(); - }); + 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); - connect(loopB, &QPushButton::clicked, this, [this]() + int circleRingPt = 0; + for (int i = 0; i < pointCount; i++) { - state = GUIDE_LOOPING; - emit newStatus(state); + double theta = i / static_cast(pointCount) * 2 * M_PI; - capture(); - }); + 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++; + } + } + } - // Stop - connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); + 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)); + } - // Clear Calibrate - //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); - connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); + concentricRings->setLineStyle(QCPCurve::lsNone); + concentricRings->setScatterSkip(0); + concentricRings->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QColor(255, 255, 255, 150), 1)); - // Guide - connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); + concentricRings->data()->set(circleRings, true); + redTarget->data()->set(circleRed, true); + yellowTarget->data()->set(circleYellow, true); + centralTarget->data()->set(circleCentral, true); - // Connect External Guide - connect(externalConnectB, &QPushButton::clicked, this, [&]() { setBLOBEnabled(false); guider->Connect(); }); - connect(externalDisconnectB, &QPushButton::clicked, this, [&]() { setBLOBEnabled(true); guider->Disconnect(); }); + concentricRings->setPen(QPen(Qt::white)); + redTarget->setPen(QPen(Qt::red)); + yellowTarget->setPen(QPen(Qt::yellow)); + centralTarget->setPen(QPen(Qt::green)); - // Pulse Timer - pulseTimer.setSingleShot(true); - connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); + 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))); - // 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); + if (driftPlot->size().width() > 0) + driftPlot->replot(); +} - //Horizontal Axis Time Ticker Settings - QSharedPointer timeTicker(new QCPAxisTickerTime); - timeTicker->setTimeFormat("%m:%s"); - driftGraph->xAxis->setTicker(timeTicker); +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(); +} - //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)")); +void Guide::slotAutoScaleGraphs() +{ + double accuracyRadius = accuracyRadiusSpin->value(); - //Sets the default ranges - driftGraph->xAxis->setRange(0, 60, Qt::AlignRight); + double key = guideTimer.elapsed() / 1000.0; + driftGraph->xAxis->setRange(key - 60, key); 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); + driftGraph->graph(0)->rescaleValueAxis(true); + driftGraph->replot(); - //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); + driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + driftPlot->graph(0)->rescaleAxes(true); - // 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); + driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); + driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); - // 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); + driftPlot->replot(); +} - // 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)); +void Guide::guideHistory() +{ + int sliderValue = guideSlider->value(); + latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); - // 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)); + 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 - // 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); + //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(); - // 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); + driftPlot->graph(1)->addData(ra, de); //Set guide highlighted point + driftPlot->replot(); - //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); + if(!graphOnLatestPt) + { + QTime localTime = guideTimer; + localTime = localTime.addSecs(t); - connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); - connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); + 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. + } - //drift plot - double accuracyRadius = 2; + } +} - driftPlot->setBackground(QBrush(Qt::black)); - driftPlot->setSelectionTolerance(10); +void Guide::setLatestGuidePoint(bool isChecked) +{ + graphOnLatestPt = isChecked; + if(isChecked) + guideSlider->setValue(guideSlider->maximum()); +} - driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); - driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); +void Guide::toggleShowRAPlot(bool isChecked) +{ + Options::setRADisplayedOnGuideGraph(isChecked); + driftGraph->graph(0)->setVisible(isChecked); + driftGraph->graph(2)->setVisible(isChecked); + driftGraph->replot(); +} - driftPlot->xAxis->setTickPen(QPen(Qt::white, 1)); - driftPlot->yAxis->setTickPen(QPen(Qt::white, 1)); +void Guide::toggleShowDEPlot(bool isChecked) +{ + Options::setDEDisplayedOnGuideGraph(isChecked); + driftGraph->graph(1)->setVisible(isChecked); + driftGraph->graph(3)->setVisible(isChecked); + driftGraph->replot(); +} - driftPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); - driftPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); +void Guide::toggleRACorrectionsPlot(bool isChecked) +{ + Options::setRACorrDisplayedOnGuideGraph(isChecked); + driftGraph->graph(4)->setVisible(isChecked); + updateCorrectionsScaleVisibility(); +} - driftPlot->xAxis->setTickLabelColor(Qt::white); - driftPlot->yAxis->setTickLabelColor(Qt::white); +void Guide::toggleDECorrectionsPlot(bool isChecked) +{ + Options::setDECorrDisplayedOnGuideGraph(isChecked); + driftGraph->graph(5)->setVisible(isChecked); + updateCorrectionsScaleVisibility(); +} - driftPlot->xAxis->setLabelColor(Qt::white); - driftPlot->yAxis->setLabelColor(Qt::white); +void Guide::updateCorrectionsScaleVisibility() +{ + bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph()); + driftGraph->yAxis2->setVisible(isVisible); + correctionSlider->setVisible(isVisible); + driftGraph->replot(); +} - 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)); +void Guide::setCorrectionGraphScale() +{ + driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower * correctionSlider->value(), driftGraph->yAxis->range().upper * correctionSlider->value()); + driftGraph->replot(); +} - driftPlot->xAxis->setLabelPadding(2); - driftPlot->yAxis->setLabelPadding(2); +void Guide::exportGuideData() +{ + int numPoints = driftGraph->graph(0)->dataCount(); + if (numPoints == 0) + return; - 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)); + 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"); - driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); - driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); + QString path = exportFile.toLocalFile(); - driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + 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; + } - driftPlot->setInteractions(QCP::iRangeZoom); - driftPlot->setInteraction(QCP::iRangeDrag, true); + if (!exportFile.isValid()) + { + QString message = i18n("Invalid URL: %1", exportFile.url()); + KMessageBox::sorry(KStars::Instance(), message, i18n("Invalid URL")); + return; + } - driftPlot->addGraph(); - driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); - driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); + QFile file; + file.setFileName(path); + if (!file.open(QIODevice::WriteOnly)) + { + QString message = i18n("Unable to write to file %1", path); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); + return; + } - driftPlot->addGraph(); - driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); - driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); + QTextStream outstream(&file); - connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); - connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); + outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; - //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()); + 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); - //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(); + QTime localTime = guideTimer; + localTime = localTime.addSecs(t); - buildTarget(); + outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' << dePulse << ',' << endl; + } + appendLogText(i18n("Guide Data Saved as: %1", path)); + file.close(); +} - //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); +QString Guide::setRecommendedExposureValues(QList values) +{ + exposureIN->setRecommendedValues(values); + return exposureIN->getRecommendedValuesString(); +} +void Guide::addCamera(ISD::GDInterface *newCCD) +{ + ISD::CCD *ccd = static_cast(newCCD); - driftPlot->resize(190, 190); - driftPlot->replot(); + if (CCDs.contains(ccd)) + return; + if (guiderType != GUIDE_INTERNAL) + { + connect(ccd, &ISD::CCD::newBLOBManager, [ccd](INDI::Property * prop) + { + if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) + ccd->setBLOBEnabled(Options::guideRemoteImagesEnabled(), prop->getName()); + }); + guiderCombo->clear(); + guiderCombo->setEnabled(false); + if (guiderType == GUIDE_PHD2) + guiderCombo->addItem("PHD2"); + else + guiderCombo->addItem("LinGuider"); + return; + } + else + guiderCombo->setEnabled(true); + CCDs.append(ccd); - // 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(); + guiderCombo->addItem(ccd->getDeviceName()); - connect(opsGuide, &OpsGuide::settingsUpdated, [this]() - { - onThresholdChanged(Options::guideAlgorithm()); - }); + checkCCD(); +} - page = dialog->addPage(opsGuide, i18n("Guide")); - page->setIcon(QIcon::fromTheme("kstars_guides")); +void Guide::addGuideHead(ISD::GDInterface *newCCD) +{ + if (guiderType != GUIDE_INTERNAL) + return; - internalGuider->setGuideView(guideView); + ISD::CCD *ccd = static_cast(newCCD); - state = GUIDE_IDLE; + CCDs.append(ccd); - // Set current guide type - setGuiderType(-1); + QString guiderName = ccd->getDeviceName() + QString(" Guider"); - //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); -} + if (guiderCombo->findText(guiderName) == -1) + { + guiderCombo->addItem(guiderName); + //CCDs.append(static_cast (newCCD)); + } -Guide::~Guide() -{ - delete guider; + //checkCCD(CCDs.count()-1); + //guiderCombo->setCurrentIndex(CCDs.count()-1); + + //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2); } -void Guide::handleHorizontalPlotSizeChange() +void Guide::setTelescope(ISD::GDInterface *newTelescope) { - driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); - driftPlot->replot(); + currentTelescope = dynamic_cast(newTelescope); + + syncTelescopeInfo(); } -void Guide::handleVerticalPlotSizeChange() +bool Guide::setCamera(const QString &device) { - driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); - driftPlot->replot(); + 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; } -void Guide::resizeEvent(QResizeEvent *event) +QString Guide::camera() { - 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); - } + if (currentCCD) + return currentCCD->getDeviceName(); + + return QString(); } -void Guide::buildTarget() +void Guide::checkCCD(int ccdNum) { - double accuracyRadius = accuracyRadiusSpin->value(); - Options::setGuiderAccuracyThreshold(accuracyRadius); + if (guiderType != GUIDE_INTERNAL) + return; - if (centralTarget) - { - concentricRings->data()->clear(); - redTarget->data()->clear(); - yellowTarget->data()->clear(); - centralTarget->data()->clear(); - } - else + if (ccdNum == -1) { - 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); + ccdNum = guiderCombo->currentIndex(); + + if (ccdNum == -1) + return; } - 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++) + if (ccdNum <= CCDs.count()) { - double theta = i / (double)(pointCount)*2 * M_PI; + currentCCD = CCDs.at(ccdNum); - for (double ring = 1; ring < 8; ring++) + 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) { - if (ring != 4 && ring != 6) + 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); + + // If guider is external and already connected and remote images option was disabled AND it was already + // disabled, then let's go ahead and disable it. +#if 0 + if (guiderType != GUIDE_INTERNAL && Options::guideRemoteImagesEnabled() == false && guider->isConnected()) + { + for (int i = 0; i < CCDs.count(); i++) { - if (i % (9 - (int)ring) == 0) //This causes fewer points to draw on the inner circles. + ISD::CCD * oneCCD = CCDs[i]; + if (i == ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") != B_NEVER) { - circleRings[circleRingPt] = QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta), - accuracyRadius * ring * 0.25 * qSin(theta)); - circleRingPt++; + appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD1"); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD2"); + } + // If it was already disabled, enable it back + else if (i != ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") == B_NEVER) + { + appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD1"); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD2"); } } } +#endif - 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)); + targetChip->setImageView(guideView, FITS_GUIDE); + + syncCCDInfo(); } +} - concentricRings->setLineStyle(QCPCurve::lsNone); - concentricRings->setScatterSkip(0); - concentricRings->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QColor(255, 255, 255, 150), 1)); +void Guide::syncCCDInfo() +{ + INumberVectorProperty *nvp = nullptr; - concentricRings->data()->set(circleRings, true); - redTarget->data()->set(circleRed, true); - yellowTarget->data()->set(circleYellow, true); - centralTarget->data()->set(circleCentral, true); + if (currentCCD == nullptr) + return; - concentricRings->setPen(QPen(Qt::white)); - redTarget->setPen(QPen(Qt::red)); - yellowTarget->setPen(QPen(Qt::yellow)); - centralTarget->setPen(QPen(Qt::green)); + if (useGuideHead) + nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); + else + nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); - 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 (nvp) + { + INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); + if (np) + ccdPixelSizeX = np->value; - if (driftPlot->size().width() > 0) - driftPlot->replot(); + 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::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(); +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::slotAutoScaleGraphs(){ - double accuracyRadius = accuracyRadiusSpin->value(); +void Guide::syncTelescopeInfo() +{ + if (currentTelescope == nullptr || currentTelescope->isConnected() == false) + return; - double key = guideTimer.elapsed() / 1000.0; - driftGraph->xAxis->setRange(key - 60, key); - driftGraph->yAxis->setRange(-3, 3); - driftGraph->graph(0)->rescaleValueAxis(true); - driftGraph->replot(); + INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); - driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - driftPlot->graph(0)->rescaleAxes(true); + if (nvp) + { + INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); - driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); - driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); + if (np && np->value > 0) + primaryAperture = np->value; - driftPlot->replot(); + 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::guideHistory(){ - int sliderValue=guideSlider->value(); - latestCheck->setChecked(sliderValue==guideSlider->maximum()-1||sliderValue==guideSlider->maximum()); +void Guide::updateGuideParams() +{ + if (currentCCD == nullptr) + return; + + if (currentCCD->hasGuideHead() == false) + useGuideHead = false; - 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 + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - //This will allow the graph to scroll left and right along with the guide slider - if (driftGraph->xAxis->range().contains(t) == false) + if (targetChip == nullptr) { - if(t < driftGraph->xAxis->range().lower) - { - driftGraph->xAxis->setRange(t, t + driftGraph->xAxis->range().size()); - } - if(t > driftGraph->xAxis->range().upper) + appendLogText(i18n("Connection to the guide CCD is lost.")); + return; + } + + 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)) { - driftGraph->xAxis->setRange(t - driftGraph->xAxis->range().size(), t); + 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; + } } } - driftGraph->replot(); - - driftPlot->graph(1)->addData(ra, de); //Set guide highlighted point - driftPlot->replot(); - if(!graphOnLatestPt) + if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1) { - QTime localTime = guideTimer; - localTime = localTime.addSecs(t); + 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); - QPoint localTooltipCoordinates=driftGraph->graph(0)->dataPixelPosition(sliderValue).toPoint(); - QPoint globalTooltipCoordinates=driftGraph->mapToGlobal(localTooltipCoordinates); + guider->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, aperture, focal_length); + emit guideChipUpdated(targetChip); - if(raPulse == 0 && dePulse == 0) + int x, y, w, h; + if (targetChip->getFrame(&x, &y, &w, &h)) { - 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))); + 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 { - 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. + 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::setLatestGuidePoint(bool isChecked) +void Guide::addST4(ISD::ST4 *newST4) { - graphOnLatestPt=isChecked; - if(isChecked) - guideSlider->setValue(guideSlider->maximum()); + if (guiderType != GUIDE_INTERNAL) + return; + + foreach (ISD::ST4 *guidePort, ST4List) + { + if (!strcmp(guidePort->getDeviceName(), newST4->getDeviceName())) + return; + } + + ST4List.append(newST4); + + ST4Combo->addItem(newST4->getDeviceName()); + + setST4(0); } -void Guide::toggleShowRAPlot(bool isChecked) +bool Guide::setST4(const QString &device) { - Options::setRADisplayedOnGuideGraph(isChecked); - driftGraph->graph(0)->setVisible(isChecked); - driftGraph->graph(2)->setVisible(isChecked); - driftGraph->replot(); + 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; } -void Guide::toggleShowDEPlot(bool isChecked) +QString Guide::st4() { - Options::setDEDisplayedOnGuideGraph(isChecked); - driftGraph->graph(1)->setVisible(isChecked); - driftGraph->graph(3)->setVisible(isChecked); - driftGraph->replot(); + if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) + return QString(); + + return ST4Combo->currentText(); } -void Guide::toggleRACorrectionsPlot(bool isChecked) +void Guide::setST4(int index) { - Options::setRACorrDisplayedOnGuideGraph(isChecked); - driftGraph->graph(4)->setVisible(isChecked); - updateCorrectionsScaleVisibility(); + if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) + return; + + ST4Driver = ST4List.at(index); + + GuideDriver = ST4Driver; } -void Guide::toggleDECorrectionsPlot(bool isChecked) +void Guide::setAO(ISD::ST4 *newAO) { - Options::setDECorrDisplayedOnGuideGraph(isChecked); - driftGraph->graph(5)->setVisible(isChecked); - updateCorrectionsScaleVisibility(); + AODriver = newAO; + //guider->setAO(true); } -void Guide::updateCorrectionsScaleVisibility() +bool Guide::capture() { - bool isVisible=(Options::rACorrDisplayedOnGuideGraph()||Options::dECorrDisplayedOnGuideGraph()); - driftGraph->yAxis2->setVisible(isVisible); - correctionSlider->setVisible(isVisible); - driftGraph->replot(); -} + buildOperationStack(GUIDE_CAPTURE); -void Guide::setCorrectionGraphScale(){ - driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower*correctionSlider->value(),driftGraph->yAxis->range().upper*correctionSlider->value()); - driftGraph->replot(); + return executeOperationStack(); } -void Guide::exportGuideData() +bool Guide::captureOneFrame() { - 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"); + captureTimeout.stop(); - QString path = exportFile.toLocalFile(); + if (currentCCD == nullptr) + return false; - if (QFile::exists(path)) + if (currentCCD->isConnected() == false) { - 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; + appendLogText(i18n("Error: lost connection to CCD.")); + return false; } - if (!exportFile.isValid()) + // 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)) { - QString message = i18n("Invalid URL: %1", exportFile.url()); - KMessageBox::sorry(KStars::Instance(), message, i18n("Invalid URL")); - return; + QVariantMap settings = frameSettings[targetChip]; + targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), + settings["h"].toInt()); } - QFile file; - file.setFileName(path); - if (!file.open(QIODevice::WriteOnly)) +#if 0 + switch (state) { - QString message = i18n("Unable to write to file %1", path); - KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); - return; - } + case GUIDE_GUIDING: + if (Options::rapidGuideEnabled() == false) + connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, &Ekos::Guide::newFITS(IBLOB *)), Qt::UniqueConnection); + targetChip->capture(seqExpose); + return true; + break; - QTextStream outstream(&file); + default: + break; + } +#endif - outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; + currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); - 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); + connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection); + qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; - QTime localTime = guideTimer; - localTime = localTime.addSecs(t); + double finalExposure = seqExpose; - outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' << dePulse << ',' << endl; - } - appendLogText(i18n("Guide Data Saved as: %1", path)); - file.close(); -} + // 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; -QString Guide::setRecommendedExposureValues(QList values) -{ - exposureIN->setRecommendedValues(values); - return exposureIN->getRecommendedValuesString(); + // Timeout is exposure duration + timeout threshold in seconds + captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD); + + targetChip->capture(finalExposure); + + return true; } -void Guide::addCamera(ISD::GDInterface *newCCD) +bool Guide::abort() { - ISD::CCD *ccd = static_cast(newCCD); - - if (CCDs.contains(ccd)) - return; - if (guiderType != GUIDE_INTERNAL) + if (currentCCD && guiderType == GUIDE_INTERNAL) { - connect(ccd, &ISD::CCD::newBLOBManager, [ccd](INDI::Property *prop) { - if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) - ccd->setBLOBEnabled(Options::guideRemoteImagesEnabled(), prop->getName()); - }); - guiderCombo->clear(); - guiderCombo->setEnabled(false); - if (guiderType == GUIDE_PHD2) - guiderCombo->addItem("PHD2"); - else - guiderCombo->addItem("LinGuider"); - return; + captureTimeout.stop(); + pulseTimer.stop(); + ISD::CCDChip *targetChip = + currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (targetChip->isCapturing()) + targetChip->abortExposure(); } - else - guiderCombo->setEnabled(true); - CCDs.append(ccd); + manualDitherB->setEnabled(false); - guiderCombo->addItem(ccd->getDeviceName()); + setBusy(false); - checkCCD(); + switch (state) + { + case GUIDE_IDLE: + case GUIDE_CONNECTED: + setBLOBEnabled(false); + break; + case GUIDE_DISCONNECTED: + setBLOBEnabled(true); + 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::addGuideHead(ISD::GDInterface *newCCD) +void Guide::setBusy(bool enable) { - if (guiderType != GUIDE_INTERNAL) + if (enable && pi->isAnimated()) + return; + else if (enable == false && pi->isAnimated() == false) return; - ISD::CCD *ccd = static_cast(newCCD); + if (enable) + { + clearCalibrationB->setEnabled(false); + guideB->setEnabled(false); + captureB->setEnabled(false); + loopB->setEnabled(false); - CCDs.append(ccd); + darkFrameCheck->setEnabled(false); + subFrameCheck->setEnabled(false); + autoStarCheck->setEnabled(false); - QString guiderName = ccd->getDeviceName() + QString(" Guider"); + stopB->setEnabled(true); - if (guiderCombo->findText(guiderName) == -1) - { - guiderCombo->addItem(guiderName); - //CCDs.append(static_cast (newCCD)); + pi->startAnimation(); + + //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); } + else + { + if (guiderType == GUIDE_INTERNAL) + { + captureB->setEnabled(true); + loopB->setEnabled(true); + darkFrameCheck->setEnabled(true); + subFrameCheck->setEnabled(true); + autoStarCheck->setEnabled(true); + } - //checkCCD(CCDs.count()-1); - //guiderCombo->setCurrentIndex(CCDs.count()-1); + if (calibrationComplete) + clearCalibrationB->setEnabled(true); + guideB->setEnabled(true); + stopB->setEnabled(false); + pi->stopAnimation(); - //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2); + connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); + } } -void Guide::setTelescope(ISD::GDInterface *newTelescope) +void Guide::processCaptureTimeout() { - currentTelescope = dynamic_cast(newTelescope); - - syncTelescopeInfo(); -} + captureTimeoutCounter++; -bool Guide::setCamera(const QString &device) -{ - if (guiderType != GUIDE_INTERNAL) - return true; + if (captureTimeoutCounter >= 3) + { + captureTimeoutCounter = 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.")); - for (int i = 0; i < guiderCombo->count(); i++) - if (device == guiderCombo->itemText(i)) - { - guiderCombo->setCurrentIndex(i); - checkCCD(i); - return true; - } + abort(); + return; + } - return false; + 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); } -QString Guide::camera() +void Guide::newFITS(IBLOB *bp) { - if (currentCCD) - return currentCCD->getDeviceName(); + INDI_UNUSED(bp); - return QString(); -} + captureTimeout.stop(); + captureTimeoutCounter = 0; -void Guide::checkCCD(int ccdNum) -{ - if (guiderType != GUIDE_INTERNAL) - return; + disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); - if (ccdNum == -1) - { - ccdNum = guiderCombo->currentIndex(); + qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; - if (ccdNum == -1) - return; - } + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (ccdNum <= CCDs.count()) - { - currentCCD = CCDs.at(ccdNum); - - if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) - useGuideHead = true; - else - useGuideHead = false; + int subBinX = 1, subBinY = 1; + targetChip->getBinning(&subBinX, &subBinY); - ISD::CCDChip *targetChip = - currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (targetChip && targetChip->isCapturing()) - return; + if (starCenter.x() == 0 && starCenter.y() == 0) + { + int x = 0, y = 0, w = 0, h = 0; - if (guiderType != GUIDE_INTERNAL) + if (frameSettings.contains(targetChip)) { - syncCCDInfo(); - return; + 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); - //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); - -// If guider is external and already connected and remote images option was disabled AND it was already -// disabled, then let's go ahead and disable it. -#if 0 - if (guiderType != GUIDE_INTERNAL && Options::guideRemoteImagesEnabled() == false && guider->isConnected()) - { - for (int i=0; i < CCDs.count(); i++) - { - ISD::CCD * oneCCD = CCDs[i]; - if (i == ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") != B_NEVER) - { - appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD1"); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD2"); - } - // If it was already disabled, enable it back - else if (i != ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") == B_NEVER) - { - appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD1"); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD2"); - } - } - } -#endif + starCenter.setX(w / (2 * subBinX)); + starCenter.setY(h / (2 * subBinY)); + starCenter.setZ(subBinX); + } - targetChip->setImageView(guideView, FITS_GUIDE); + syncTrackingBoxPosition(); - syncCCDInfo(); - } + setCaptureComplete(); } -void Guide::syncCCDInfo() +void Guide::setCaptureComplete() { - INumberVectorProperty *nvp = nullptr; - - if (currentCCD == nullptr) + if (operationStack.isEmpty() == false) + { + executeOperationStack(); return; + } - if (useGuideHead) - nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); - else - nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); + DarkLibrary::Instance()->disconnect(this); - if (nvp) + switch (state) { - INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); - if (np) - ccdPixelSizeX = np->value; + 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; - np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); - if (np) - ccdPixelSizeY = np->value; + case GUIDE_CAPTURE: + state = GUIDE_IDLE; + emit newStatus(state); + setBusy(false); + break; - np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); - if (np) - ccdPixelSizeY = np->value; + 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; } - updateGuideParams(); + emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } -void Guide::setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture) +void Guide::appendLogText(const QString &text) { - 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; + m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); - updateGuideParams(); + qCInfo(KSTARS_EKOS_GUIDE) << text; + + emit newLog(text); } -void Guide::syncTelescopeInfo() +void Guide::clearLog() { - if (currentTelescope == nullptr || currentTelescope->isConnected() == false) - return; + m_LogText.clear(); + emit newLog(QString()); +} - INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); +void Guide::setDECSwap(bool enable) +{ + if (ST4Driver == nullptr || guider == nullptr) + return; - if (nvp) + if (guiderType == GUIDE_INTERNAL) { - INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); + dynamic_cast(guider)->setDECSwap(enable); + ST4Driver->setDECSwap(enable); + } +} - if (np && np->value > 0) - primaryAperture = np->value; +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; - np = IUFindNumber(nvp, "GUIDER_APERTURE"); - if (np && np->value > 0) - guideAperture = np->value; + if (state == GUIDE_CALIBRATING) + pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100); - aperture = primaryAperture; + return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); +} - //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) - if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) - aperture = guideAperture; +bool Guide::sendPulse(GuideDirection dir, int msecs) +{ + if (GuideDriver == nullptr || dir == NO_DIR) + return false; - np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH"); - if (np && np->value > 0) - primaryFL = np->value; + if (state == GUIDE_CALIBRATING) + pulseTimer.start(msecs + 100); - np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH"); - if (np && np->value > 0) - guideFL = np->value; + return GuideDriver->doPulse(dir, msecs); +} - focal_length = primaryFL; +QStringList Guide::getST4Devices() +{ + QStringList devices; - //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) - if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) - focal_length = guideFL; - } + foreach (ISD::ST4 *driver, ST4List) + devices << driver->getDeviceName(); - updateGuideParams(); + return devices; } -void Guide::updateGuideParams() +#if 0 +void Guide::processRapidStarData(ISD::CCDChip * targetChip, double dx, double dy, double fit) { - if (currentCCD == nullptr) + // Check if guide star is lost + if (dx == -1 && dy == -1 && fit == -1) + { + KMessageBox::error(nullptr, i18n("Lost track of the guide star. Rapid guide aborted.")); + guider->abort(); return; + } - if (currentCCD->hasGuideHead() == false) - useGuideHead = false; - - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + FITSView * targetImage = targetChip->getImage(FITS_GUIDE); - if (targetChip == nullptr) + if (targetImage == nullptr) { - appendLogText(i18n("Connection to the guide CCD is lost.")); - return; + pmath->setImageView(nullptr); + guider->setImageView(nullptr); + calibration->setImageView(nullptr); } - binningCombo->setEnabled(targetChip->canBin()); - int subBinX = 1, subBinY = 1; - if (targetChip->canBin()) + if (rapidGuideReticleSet == false) { - 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); + // Let's set reticle parameter on first capture to those of the star, then we check if there + // is any set + double x, y, angle; + pmath->getReticleParameters(&x, &y, &angle); + pmath->setReticleParameters(dx, dy, angle); + rapidGuideReticleSet = true; } - if (frameSettings.contains(targetChip) == false) + pmath->setRapidStarData(dx, dy); + + if (guider->isDithering()) { - int x, y, w, h; - if (targetChip->getFrame(&x, &y, &w, &h)) + pmath->performProcessing(); + if (guider->dither() == false) { - 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; - } + appendLogText(i18n("Dithering failed. Autoguiding aborted.")); + emit newStatus(GUIDE_DITHERING_ERROR); + guider->abort(); + //emit ditherFailed(); } } - - if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1) + else { - 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->guide(); + capture(); + } - 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); - } +void Guide::startRapidGuide() +{ + ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - 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; - } + if (currentCCD->setRapidGuide(targetChip, true) == false) + { + appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting...")); + guider->abort(); + return; + } - // FOV in arcmin - double fov_w = (w * pixScaleX) / 60.0; - double fov_h = (h * pixScaleY) / 60.0; + rapidGuideReticleSet = false; - l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); - } + pmath->setRapidGuide(true); + currentCCD->configureRapidGuide(targetChip, true); + connect(currentCCD, SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double)), this, &Ekos::Guide::processRapidStarData(ISD::CCDChip *, double, double, double))); } -void Guide::addST4(ISD::ST4 *newST4) +void Guide::stopRapidGuide() { - if (guiderType != GUIDE_INTERNAL) - return; + ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - foreach (ISD::ST4 *guidePort, ST4List) - { - if (!strcmp(guidePort->getDeviceName(), newST4->getDeviceName())) - return; - } + pmath->setRapidGuide(false); - ST4List.append(newST4); + rapidGuideReticleSet = false; - ST4Combo->addItem(newST4->getDeviceName()); + currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double))); - setST4(0); + currentCCD->configureRapidGuide(targetChip, false, false, false); + + currentCCD->setRapidGuide(targetChip, false); } +#endif -bool Guide::setST4(const QString &device) +bool Guide::calibrate() { - if (guiderType != GUIDE_INTERNAL) - return true; + // Set status to idle and let the operations change it as they get executed + state = GUIDE_IDLE; + emit newStatus(state); - for (int i = 0; i < ST4List.count(); i++) - if (ST4List.at(i)->getDeviceName() == device) + if (guiderType == GUIDE_INTERNAL) + { + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + + if (frameSettings.contains(targetChip)) { - ST4Combo->setCurrentIndex(i); - setST4(i); - return true; - } + 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; - return false; -} + subFramed = false; + } + } -QString Guide::st4() -{ - if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) - return QString(); + saveSettings(); - return ST4Combo->currentText(); -} + buildOperationStack(GUIDE_CALIBRATING); -void Guide::setST4(int index) -{ - if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) - return; + executeOperationStack(); - ST4Driver = ST4List.at(index); + qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); - GuideDriver = ST4Driver; + return true; } -void Guide::setAO(ISD::ST4 *newAO) +bool Guide::guide() { - AODriver = newAO; - //guider->setAO(true); -} + if (Options::defaultCaptureCCD() == guiderCombo->currentText()) + { + if (KMessageBox::questionYesNo(nullptr, i18n("The guide camera is identical to the capture camera. Are you sure you want to continue?")) == + KMessageBox::No) + return false; + } -bool Guide::capture() -{ - buildOperationStack(GUIDE_CAPTURE); + if(guiderType != GUIDE_PHD2) + { + if (calibrationComplete == false) + return calibrate(); + } - return executeOperationStack(); -} + saveSettings(); -bool Guide::captureOneFrame() -{ - captureTimeout.stop(); + bool rc = guider->guide(); - if (currentCCD == nullptr) - return false; + return rc; +} - if (currentCCD->isConnected() == false) +bool Guide::dither() +{ + if (Options::ditherNoGuiding() && state == GUIDE_IDLE) { - appendLogText(i18n("Error: lost connection to CCD.")); - return false; + ditherDirectly(); + return true; } - // If CCD Telescope Type does not match desired scope type, change it - if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex()) - currentCCD->setTelescopeType(static_cast(FOVScopeCombo->currentIndex())); + if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) + return true; - double seqExpose = exposureIN->value(); + //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)); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (guiderType == GUIDE_INTERNAL) + { + if (state != GUIDE_GUIDING) + capture(); - targetChip->setCaptureMode(FITS_GUIDE); - targetChip->setFrameType(FRAME_LIGHT); + setStatus(GUIDE_DITHERING); - if (darkFrameCheck->isChecked()) - targetChip->setCaptureFilter(FITS_NONE); + return true; + } else - targetChip->setCaptureFilter((FITSScale)filterCombo->currentIndex()); + return guider->dither(Options::ditherPixels()); +} - guideView->setBaseSize(guideWidget->size()); - setBusy(true); +bool Guide::suspend() +{ + if (state == GUIDE_SUSPENDED) + return true; + else if (state >= GUIDE_CAPTURE) + return guider->suspend(); + else + return false; +} - if (frameSettings.contains(targetChip)) - { - QVariantMap settings = frameSettings[targetChip]; - targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), - settings["h"].toInt()); - } +bool Guide::resume() +{ + if (state == GUIDE_GUIDING) + return true; + else if (state == GUIDE_SUSPENDED) + return guider->resume(); + else + return false; +} -#if 0 - switch (state) +void Guide::setCaptureStatus(CaptureState newState) +{ + switch (newState) { - case GUIDE_GUIDING: - if (Options::rapidGuideEnabled() == false) - connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, &Ekos::Guide::newFITS(IBLOB *)), Qt::UniqueConnection); - targetChip->capture(seqExpose); - return true; + case CAPTURE_DITHERING: + dither(); break; default: break; } -#endif +} - currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); +void Guide::setPierSide(ISD::Telescope::PierSide newSide) +{ + Q_UNUSED(newSide); - 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; + // 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.")); + } } -bool Guide::abort() +void Guide::setMountStatus(ISD::Telescope::Status newState) { - if (currentCCD && guiderType == GUIDE_INTERNAL) + // If we're guiding, and the mount either slews or parks, then we abort. + if ((state == GUIDE_GUIDING || state == GUIDE_DITHERING) && (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING)) { - captureTimeout.stop(); - pulseTimer.stop(); - ISD::CCDChip *targetChip = - currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (targetChip->isCapturing()) - targetChip->abortExposure(); - } + if (newState == ISD::Telescope::MOUNT_PARKING) + appendLogText(i18n("Mount is parking. Aborting guide...")); + else + appendLogText(i18n("Mount is slewing. Aborting guide...")); - manualDitherB->setEnabled(false); + abort(); + } - setBusy(false); + if (guiderType != GUIDE_INTERNAL) + return; - switch (state) + switch (newState) { - case GUIDE_IDLE: - case GUIDE_CONNECTED: - setBLOBEnabled(false); - break; - case GUIDE_DISCONNECTED: - setBLOBEnabled(true); - break; - - case GUIDE_CALIBRATING: - case GUIDE_DITHERING: - case GUIDE_STAR_SELECT: - case GUIDE_CAPTURE: - case GUIDE_GUIDING: - case GUIDE_LOOPING: - guider->abort(); + 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: - break; + if (pi->isAnimated() == false) + { + captureB->setEnabled(true); + loopB->setEnabled(true); + clearCalibrationB->setEnabled(true); + } } - - return true; } -void Guide::setBusy(bool enable) +void Guide::setExposure(double value) { - 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); - - stopB->setEnabled(true); - - pi->startAnimation(); + exposureIN->setValue(value); +} - //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); - } - else - { - if (guiderType == GUIDE_INTERNAL) +void Guide::setImageFilter(const QString &value) +{ + for (int i = 0; i < filterCombo->count(); i++) + if (filterCombo->itemText(i) == value) { - captureB->setEnabled(true); - loopB->setEnabled(true); - darkFrameCheck->setEnabled(true); - subFrameCheck->setEnabled(true); + filterCombo->setCurrentIndex(i); + break; } +} - if (calibrationComplete) - clearCalibrationB->setEnabled(true); - guideB->setEnabled(true); - stopB->setEnabled(false); - pi->stopAnimation(); +void Guide::setCalibrationTwoAxis(bool enable) +{ + Options::setTwoAxisEnabled(enable); +} - connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); - } +void Guide::setCalibrationAutoStar(bool enable) +{ + autoStarCheck->setChecked(enable); } -void Guide::processCaptureTimeout() +void Guide::setCalibrationAutoSquareSize(bool enable) { - captureTimeoutCounter++; + Options::setGuideAutoSquareSizeEnabled(enable); +} - if (captureTimeoutCounter >= 3) - { - captureTimeoutCounter = 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.")); +void Guide::setCalibrationPulseDuration(int pulseDuration) +{ + Options::setCalibrationPulseDuration(pulseDuration); +} - abort(); - return; - } +void Guide::setGuideBoxSizeIndex(int index) +{ + Options::setGuideSquareSizeIndex(index); +} - 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); +void Guide::setGuideAlgorithmIndex(int index) +{ + Options::setGuideAlgorithm(index); } -void Guide::newFITS(IBLOB *bp) +void Guide::setSubFrameEnabled(bool enable) { - INDI_UNUSED(bp); + Options::setGuideSubframeEnabled(enable); + if (subFrameCheck->isChecked() != enable) + subFrameCheck->setChecked(enable); +} - captureTimeout.stop(); - captureTimeoutCounter = 0; +#if 0 +void Guide::setGuideRapidEnabled(bool enable) +{ + //guider->setGuideOptions(guider->getAlgorithm(), guider->useSubFrame() , enable); +} +#endif - disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); +void Guide::setDitherSettings(bool enable, double value) +{ + Options::setDitherEnabled(enable); + Options::setDitherPixels(value); +} - qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; +#if 0 +void Guide::startAutoCalibrateGuide() +{ + // A must for auto stuff + Options::setGuideAutoStarEnabled(true); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (Options::resetGuideCalibration()) + clearCalibration(); - int subBinX = 1, subBinY = 1; - targetChip->getBinning(&subBinX, &subBinY); + guide(); - if (starCenter.x() == 0 && starCenter.y() == 0) +#if 0 + if (guiderType == GUIDE_INTERNAL) { - 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); + calibrationComplete = false; + autoCalibrateGuide = true; + calibrate(); + } + else + { + calibrationComplete = true; + autoCalibrateGuide = true; + guide(); } +#endif +} +#endif - syncTrackingBoxPosition(); +void Guide::clearCalibration() +{ + calibrationComplete = false; - setCaptureComplete(); + guider->clearCalibration(); + + appendLogText(i18n("Calibration is cleared.")); } -void Guide::setCaptureComplete() +void Guide::setStatus(Ekos::GuideState newState) { - if (operationStack.isEmpty() == false) - { - executeOperationStack(); + if (newState == state) return; - } - DarkLibrary::Instance()->disconnect(this); + GuideState previousState = state; + + state = newState; + emit newStatus(state); 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); + appendLogText(i18n("External guider connected.")); + externalConnectB->setEnabled(false); + externalDisconnectB->setEnabled(true); + captureB->setEnabled(false); + loopB->setEnabled(false); + clearCalibrationB->setEnabled(true); + guideB->setEnabled(true); + setBLOBEnabled(false); break; - case GUIDE_CAPTURE: - state = GUIDE_IDLE; - emit newStatus(state); - setBusy(false); + 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); + setBLOBEnabled(true); +#ifdef Q_OS_OSX + repaint(); //This is a band-aid for a bug in QT 5.10.0 +#endif break; - case GUIDE_LOOPING: - capture(); + 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: - guider->calibrate(); + appendLogText(i18n("Calibration started.")); + setBusy(true); break; case GUIDE_GUIDING: - guider->guide(); + 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_DITHERING: - guider->dither(Options::ditherPixels()); + case GUIDE_ABORTED: + appendLogText(i18n("Autoguiding aborted.")); + setBusy(false); break; - // Feature only of internal guider - case GUIDE_MANUAL_DITHERING: - dynamic_cast(guider)->processManualDithering(); + case GUIDE_SUSPENDED: + appendLogText(i18n("Guiding suspended.")); break; case GUIDE_REACQUIRE: - guider->reacquire(); - break; + 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::ditherNoGuiding()) - return; + 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; } - - emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } -void Guide::appendLogText(const QString &text) +void Guide::updateCCDBin(int index) { - m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", - QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); + if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL) + return; - qCInfo(KSTARS_EKOS_GUIDE) << text; + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - emit newLog(text); -} + targetChip->setBinning(index + 1, index + 1); -void Guide::clearLog() -{ - m_LogText.clear(); - emit newLog(QString()); + 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::setDECSwap(bool enable) +void Guide::processCCDNumber(INumberVectorProperty *nvp) { - if (ST4Driver == nullptr || guider == nullptr) + if (currentCCD == nullptr || strcmp(nvp->device, currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) return; - if (guiderType == GUIDE_INTERNAL) + if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || + (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead)) { - dynamic_cast(guider)->setDECSwap(enable); - ST4Driver->setDECSwap(enable); + binningCombo->disconnect(); + binningCombo->setCurrentIndex(nvp->np[0].value - 1); + connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin); } } -bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) +void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) { - if (GuideDriver == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR)) - return false; + if (guiderType != GUIDE_INTERNAL) + return; - if (state == GUIDE_CALIBRATING) - pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100); + INDI_UNUSED(exposure); - return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); + 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()); + } } -bool Guide::sendPulse(GuideDirection dir, int msecs) +void Guide::setDarkFrameEnabled(bool enable) { - if (GuideDriver == nullptr || dir == NO_DIR) - return false; - - if (state == GUIDE_CALIBRATING) - pulseTimer.start(msecs + 100); - - return GuideDriver->doPulse(dir, msecs); + Options::setGuideDarkFrameEnabled(enable); + if (darkFrameCheck->isChecked() != enable) + darkFrameCheck->setChecked(enable); } -QStringList Guide::getST4Devices() +void Guide::saveDefaultGuideExposure() { - QStringList devices; + Options::setGuideExposure(exposureIN->value()); + if(guiderType == GUIDE_PHD2) + phd2Guider->requestSetExposureTime(exposureIN->value() * 1000); +} - foreach (ISD::ST4 *driver, ST4List) - devices << driver->getDeviceName(); +void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) +{ + starCenter.setX(newCenter.x()); + starCenter.setY(newCenter.y()); + if (newCenter.z() > 0) + starCenter.setZ(newCenter.z()); - return devices; + if (updateNow) + syncTrackingBoxPosition(); } -#if 0 -void Guide::processRapidStarData(ISD::CCDChip * targetChip, double dx, double dy, double fit) +void Guide::syncTrackingBoxPosition() { - // Check if guide star is lost - if (dx == -1 && dy == -1 && fit == -1) + 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) { - KMessageBox::error(nullptr, i18n("Lost track of the guide star. Rapid guide aborted.")); - guider->abort(); - return; + 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); } +} - FITSView * targetImage = targetChip->getImage(FITS_GUIDE); +bool Guide::setGuiderType(int type) +{ + // Use default guider option + if (type == -1) + type = Options::guiderType(); + else if (type == guiderType) + return true; - if (targetImage == nullptr) + if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING) { - pmath->setImageView(nullptr); - guider->setImageView(nullptr); - calibration->setImageView(nullptr); + appendLogText(i18n("Cannot change guider type while active.")); + return false; } - if (rapidGuideReticleSet == false) + if (guider != nullptr) { - // Let's set reticle parameter on first capture to those of the star, then we check if there - // is any set - double x,y,angle; - pmath->getReticleParameters(&x, &y, &angle); - pmath->setReticleParameters(dx, dy, angle); - rapidGuideReticleSet = true; + // Disconnect from host + if (guider->isConnected()) + guider->Disconnect(); + + // Disconnect signals + guider->disconnect(); } - pmath->setRapidStarData(dx, dy); + guiderType = static_cast(type); - if (guider->isDithering()) + switch (type) { - pmath->performProcessing(); - if (guider->dither() == false) + case GUIDE_INTERNAL: { - appendLogText(i18n("Dithering failed. Autoguiding aborted.")); - emit newStatus(GUIDE_DITHERING_ERROR); - guider->abort(); - //emit ditherFailed(); - } - } - else - { - guider->guide(); - capture(); - } + 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; -void Guide::startRapidGuide() -{ - ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); + internalGuider->setRegionAxis(opsGuide->kcfg_GuideRegionAxis->currentText().toInt()); - if (currentCCD->setRapidGuide(targetChip, true) == false) - { - appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting...")); - guider->abort(); - return; - } + clearCalibrationB->setEnabled(true); + guideB->setEnabled(true); + captureB->setEnabled(true); + loopB->setEnabled(true); + darkFrameCheck->setEnabled(true); + subFrameCheck->setEnabled(true); + autoStarCheck->setEnabled(true); - rapidGuideReticleSet = false; + guiderCombo->setEnabled(true); + ST4Combo->setEnabled(true); + exposureIN->setEnabled(true); + binningCombo->setEnabled(true); + boxSizeCombo->setEnabled(true); + filterCombo->setEnabled(true); - pmath->setRapidGuide(true); - currentCCD->configureRapidGuide(targetChip, true); - connect(currentCCD, SIGNAL(newGuideStarData(ISD::CCDChip*,double,double,double)), this, &Ekos::Guide::processRapidStarData(ISD::CCDChip *,double,double,double))); -} + externalConnectB->setEnabled(false); + externalDisconnectB->setEnabled(false); -void Guide::stopRapidGuide() -{ - ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + 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); - pmath->setRapidGuide(false); + guiderCombo->setToolTip(i18n("Select guide camera.")); - rapidGuideReticleSet = false; + updateGuideParams(); + } + break; - currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*,double,double,double))); + case GUIDE_PHD2: + if (phd2Guider.isNull()) + phd2Guider = new PHD2(); - currentCCD->configureRapidGuide(targetChip, false, false, false); + guider = phd2Guider; + phd2Guider->setGuideView(guideView); - currentCCD->setRapidGuide(targetChip, false); -} -#endif + connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); -bool Guide::calibrate() -{ - // Set status to idle and let the operations change it as they get executed - state = GUIDE_IDLE; - emit newStatus(state); + 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); - if (guiderType == GUIDE_INTERNAL) - { - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + checkBox_DirRA->setEnabled(false); + eastControlCheck->setEnabled(false); + westControlCheck->setEnabled(false); + swapCheck->setEnabled(false); - 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; - } - } + 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); - saveSettings(); + ST4Combo->setEnabled(false); + exposureIN->setEnabled(true); + binningCombo->setEnabled(false); + boxSizeCombo->setEnabled(false); + filterCombo->setEnabled(false); - buildOperationStack(GUIDE_CALIBRATING); + if (Options::guideRemoteImagesEnabled() == false) + { + //guiderCombo->setCurrentIndex(-1); + guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); + } + else + guiderCombo->setEnabled(false); - executeOperationStack(); + if (Options::resetGuideCalibration()) + appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); - qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); + updateGuideParams(); + break; - return true; -} + case GUIDE_LINGUIDER: + if (linGuider.isNull()) + linGuider = new LinGuider(); -bool Guide::guide() -{ - if (Options::defaultCaptureCCD() == guiderCombo->currentText()) - { - if (KMessageBox::questionYesNo(nullptr, i18n("The guide camera is identical to the capture camera. Are you sure you want to continue?"))== - KMessageBox::No) - return false; - } + guider = linGuider; - if(guiderType != GUIDE_PHD2) - { - if (calibrationComplete == false) - return calibrate(); + 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); + + if (Options::guideRemoteImagesEnabled() == false) + { + guiderCombo->setCurrentIndex(-1); + guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); + } + else + guiderCombo->setEnabled(false); + + updateGuideParams(); + + break; } - saveSettings(); + 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); - bool rc = guider->guide(); + 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); + } - return rc; -} + externalConnectB->setEnabled(false); + externalDisconnectB->setEnabled(false); -bool Guide::dither() -{ - if (Options::ditherNoGuiding() && state == GUIDE_IDLE) + if (guider != nullptr && guiderType != GUIDE_INTERNAL) { - ditherDirectly(); - return true; + externalConnectB->setEnabled(!guider->isConnected()); + externalDisconnectB->setEnabled(guider->isConnected()); } - if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) - return true; + if (guider != nullptr) + guider->Connect(); - //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)); + return true; +} - if (guiderType == GUIDE_INTERNAL) +void Guide::updateTrackingBoxSize(int currentIndex) +{ + if (currentIndex >= 0) { - if (state != GUIDE_GUIDING) - capture(); + Options::setGuideSquareSizeIndex(currentIndex); - setStatus(GUIDE_DITHERING); + if (guiderType == GUIDE_INTERNAL) + dynamic_cast(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt()); - return true; + syncTrackingBoxPosition(); } - 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() +/* +void Guide::onXscaleChanged( int i ) { - if (state == GUIDE_GUIDING) - return true; - else if (state == GUIDE_SUSPENDED) - return guider->resume(); - else - return false; -} + int rx, ry; -void Guide::setCaptureStatus(CaptureState newState) -{ - switch (newState) - { - case CAPTURE_DITHERING: - dither(); - break; + driftGraphics->getVisibleRanges( &rx, &ry ); + driftGraphics->setVisibleRanges( i*driftGraphics->getGridN(), ry ); + driftGraphics->update(); - default: - break; - } } -void Guide::setPierSide(ISD::Telescope::PierSide newSide) +void Guide::onYscaleChanged( int i ) { - Q_UNUSED(newSide); + int rx, ry; - // 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.")); - } + driftGraphics->getVisibleRanges( &rx, &ry ); + driftGraphics->setVisibleRanges( rx, i*driftGraphics->getGridN() ); + driftGraphics->update(); } +*/ -void Guide::setMountStatus(ISD::Telescope::Status newState) +void Guide::onThresholdChanged(int index) { - // If we're guiding, and the mount either slews or parks, then we abort. - if ((state == GUIDE_GUIDING || state == GUIDE_DITHERING) && (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING)) - { - 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) + switch (guiderType) { - case ISD::Telescope::MOUNT_SLEWING: - case ISD::Telescope::MOUNT_PARKING: - case ISD::Telescope::MOUNT_MOVING: - captureB->setEnabled(false); - loopB->setEnabled(false); - clearCalibrationB->setEnabled(false); + case GUIDE_INTERNAL: + dynamic_cast(guider)->setSquareAlgorithm(index); break; default: - if (pi->isAnimated() == false) - { - captureB->setEnabled(true); - loopB->setEnabled(true); - clearCalibrationB->setEnabled(true); - } + break; } } -void Guide::setExposure(double value) +void Guide::onInfoRateChanged(double val) { - exposureIN->setValue(value); -} + Options::setGuidingRate(val); -void Guide::setImageFilter(const QString &value) -{ - for (int i = 0; i < filterCombo->count(); i++) - if (filterCombo->itemText(i) == value) - { - filterCombo->setCurrentIndex(i); - break; - } -} + double gain = 0; -void Guide::setCalibrationTwoAxis(bool enable) -{ - Options::setTwoAxisEnabled(enable); -} + if (val > 0.01) + gain = 1000.0 / (val * 15.0); -void Guide::setCalibrationAutoStar(bool enable) -{ - Options::setGuideAutoStarEnabled(enable); + l_RecommendedGain->setText(i18n("P: %1", QString().setNum(gain, 'f', 2))); } -void Guide::setCalibrationAutoSquareSize(bool enable) +void Guide::onEnableDirRA(bool enable) { - Options::setGuideAutoSquareSizeEnabled(enable); + Options::setRAGuideEnabled(enable); } -void Guide::setCalibrationPulseDuration(int pulseDuration) +void Guide::onEnableDirDEC(bool enable) { - Options::setCalibrationPulseDuration(pulseDuration); + Options::setDECGuideEnabled(enable); + updatePHD2Directions(); } -void Guide::setGuideBoxSizeIndex(int index) +void Guide::syncSettings() { - Options::setGuideSquareSizeIndex(index); -} + QSpinBox *pSB = nullptr; + QDoubleSpinBox *pDSB = nullptr; + QCheckBox *pCB = nullptr; -void Guide::setGuideAlgorithmIndex(int index) -{ - Options::setGuideAlgorithm(index); -} + QObject *obj = sender(); -void Guide::setSubFrameEnabled(bool enable) -{ - Options::setGuideSubframeEnabled(enable); - if (subFrameCheck->isChecked() != enable) - subFrameCheck->setChecked(enable); + 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()); + } } -#if 0 -void Guide::setGuideRapidEnabled(bool enable) +void Guide::onControlDirectionChanged(bool enable) { - //guider->setGuideOptions(guider->getAlgorithm(), guider->useSubFrame() , enable); -} -#endif + QObject *obj = sender(); -void Guide::setDitherSettings(bool enable, double value) + 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() { - Options::setDitherEnabled(enable); - Options::setDitherPixels(value); + if(guiderType == GUIDE_PHD2) + phd2Guider -> requestSetDEGuideMode(checkBox_DirDEC->isChecked(), northControlCheck->isChecked(), southControlCheck->isChecked()); } - -#if 0 -void Guide::startAutoCalibrateGuide() +void Guide::updateDirectionsFromPHD2(QString mode) { - // A must for auto stuff - Options::setGuideAutoStarEnabled(true); - - if (Options::resetGuideCalibration()) - clearCalibration(); - - guide(); + //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 0 - if (guiderType == GUIDE_INTERNAL) + if(mode == "Auto") { - calibrationComplete = false; - autoCalibrateGuide = true; - calibrate(); + checkBox_DirDEC->setChecked(true); + northControlCheck->setChecked(true); + southControlCheck->setChecked(true); } - else + else if(mode == "North") { - calibrationComplete = true; - autoCalibrateGuide = true; - guide(); + 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); } -#endif -} -#endif - -void Guide::clearCalibration() -{ - calibrationComplete = false; - - guider->clearCalibration(); - appendLogText(i18n("Calibration is cleared.")); + //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::setStatus(Ekos::GuideState newState) +#if 0 +void Guide::onRapidGuideChanged(bool enable) { - if (newState == state) + if (m_isStarted) + { + guideModule->appendLogText(i18n("You must stop auto guiding before changing this setting.")); return; + } - GuideState previousState = state; - - state = newState; - emit newStatus(state); + m_useRapidGuide = enable; - switch (state) + if (m_useRapidGuide) { - case GUIDE_CONNECTED: - appendLogText(i18n("External guider connected.")); - externalConnectB->setEnabled(false); - externalDisconnectB->setEnabled(true); - captureB->setEnabled(false); - loopB->setEnabled(false); - clearCalibrationB->setEnabled(true); - guideB->setEnabled(true); - setBLOBEnabled(false); - break; + guideModule->appendLogText(i18n("Rapid Guiding is enabled. Guide star will be determined automatically by the CCD driver. No frames are sent to Ekos unless explicitly enabled by the user in the CCD driver settings.")); + } + else + guideModule->appendLogText(i18n("Rapid Guiding is disabled.")); +} +#endif - 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); - setBLOBEnabled(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; +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()); +} - case GUIDE_IDLE: - case GUIDE_CALIBRATION_ERROR: - setBusy(false); - manualDitherB->setEnabled(false); - break; +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()); +} - case GUIDE_CALIBRATING: - appendLogText(i18n("Calibration started.")); - setBusy(true); - break; +void Guide::setTrackingStar(int x, int y) +{ + QVector3D newStarPosition(x, y, -1); + setStarPosition(newStarPosition, true); - case GUIDE_GUIDING: - if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) - appendLogText(i18n("Guiding resumed.")); - else - { - appendLogText(i18n("Autoguiding started.")); - setBusy(true); + /*if (state == GUIDE_STAR_SELECT) + { + guider->setStarPosition(newStarPosition); + guider->calibrate(); + }*/ - clearGuideGraphs(); - guideTimer = QTime::currentTime(); - refreshColorScheme(); - } - manualDitherB->setEnabled(true); + if (operationStack.isEmpty() == false) + executeOperationStack(); +} - break; +void Guide::setAxisDelta(double ra, double de) +{ + // Time since timer started. + double key = guideTimer.elapsed() / 1000.0; - case GUIDE_ABORTED: - appendLogText(i18n("Autoguiding aborted.")); - setBusy(false); - break; + ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. - case GUIDE_SUSPENDED: - appendLogText(i18n("Guiding suspended.")); - break; + driftGraph->graph(0)->addData(key, ra); + driftGraph->graph(1)->addData(key, de); - case GUIDE_REACQUIRE: - capture(); - break; + int currentNumPoints = driftGraph->graph(0)->dataCount(); + guideSlider->setMaximum(currentNumPoints); + if(graphOnLatestPt) + guideSlider->setValue(currentNumPoints); - case GUIDE_MANUAL_DITHERING: - appendLogText(i18n("Manual dithering in progress.")); - break; + // Expand range if it doesn't fit already + if (driftGraph->yAxis->range().contains(ra) == false) + driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); - case GUIDE_DITHERING: - appendLogText(i18n("Dithering in progress.")); - break; + if (driftGraph->yAxis->range().contains(de) == false) + driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); - 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; + // 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(); - 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; + //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 + } - 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; + 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(); + }); } -} -void Guide::updateCCDBin(int index) -{ - if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL) - return; + driftPlot->replot(); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + l_DeltaRA->setText(QString::number(ra, 'f', 2)); + l_DeltaDEC->setText(QString::number(de, 'f', 2)); - targetChip->setBinning(index + 1, index + 1); + emit newAxisDelta(ra, de); - QVariantMap settings = frameSettings[targetChip]; - settings["binx"] = index + 1; - settings["biny"] = index + 1; - frameSettings[targetChip] = settings; + profilePixmap = driftGraph->grab(); + emit newProfilePixmap(profilePixmap); +} - guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), - settings["binx"].toInt(), settings["biny"].toInt()); +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(sqrt(ra * ra + de * de), 'f', 2)); + emit newAxisSigma(ra, de); } -void Guide::processCCDNumber(INumberVectorProperty *nvp) +QList Guide::axisDelta() { - if (currentCCD == nullptr || strcmp(nvp->device, currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) - return; + QList delta; - 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); - } + delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); + + return delta; } -void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) +QList Guide::axisSigma() { - if (guiderType != GUIDE_INTERNAL) - return; + QList sigma; - INDI_UNUSED(exposure); + sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); - 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()); - } + return sigma; } -void Guide::setDarkFrameEnabled(bool enable) +void Guide::setAxisPulse(double ra, double de) { - Options::setGuideDarkFrameEnabled(enable); - if (darkFrameCheck->isChecked() != enable) - darkFrameCheck->setChecked(enable); -} + l_PulseRA->setText(QString::number(static_cast(ra))); + l_PulseDEC->setText(QString::number(static_cast(de))); -void Guide::saveDefaultGuideExposure() -{ - Options::setGuideExposure(exposureIN->value()); - if(guiderType == GUIDE_PHD2) - phd2Guider->requestSetExposureTime(exposureIN->value()*1000); + double key = guideTimer.elapsed() / 1000.0; + + driftGraph->graph(4)->addData(key, ra); + driftGraph->graph(5)->addData(key, de); } -void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) +void Guide::refreshColorScheme() { - starCenter.setX(newCenter.x()); - starCenter.setY(newCenter.y()); - if (newCenter.z() > 0) - starCenter.setZ(newCenter.z()); + // 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)); - if (updateNow) - syncTrackingBoxPosition(); + 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::syncTrackingBoxPosition() +void Guide::driftMouseClicked(QMouseEvent *event) { - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - Q_ASSERT(targetChip); + if (event->buttons() & Qt::RightButton) + { + driftGraph->yAxis->setRange(-3, 3); + } +} - int subBinX = 1, subBinY = 1; - targetChip->getBinning(&subBinX, &subBinY); +void Guide::driftMouseOverLine(QMouseEvent *event) +{ + double key = driftGraph->xAxis->pixelToCoord(event->localPos().x()); - if (starCenter.isNull() == false) + if (driftGraph->xAxis->range().contains(key)) { - 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; - } + QCPGraph *graph = qobject_cast(driftGraph->plottableAt(event->pos(), false)); - // If binning changed, update coords accordingly - if (subBinX != starCenter.z()) + if (graph) { - if (starCenter.z() > 0) + 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) { - starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); - starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); + 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. } - - starCenter.setZ(subBinX); } + else + QToolTip::hideText(); - QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), - boxSize / subBinX, boxSize / subBinY); - guideView->setTrackingBoxEnabled(true); - guideView->setTrackingBox(starRect); + driftGraph->replot(); } } -bool Guide::setGuiderType(int type) +void Guide::buildOperationStack(GuideState operation) { - // 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; - } + operationStack.clear(); - if (guider != nullptr) + switch (operation) { - // Disconnect from host - if (guider->isConnected()) - guider->Disconnect(); + case GUIDE_CAPTURE: + if (Options::guideDarkFrameEnabled()) + operationStack.push(GUIDE_DARK); - // Disconnect signals - guider->disconnect(); - } + operationStack.push(GUIDE_CAPTURE); + operationStack.push(GUIDE_SUBFRAME); + break; - guiderType = static_cast(type); + case GUIDE_CALIBRATING: + operationStack.push(GUIDE_CALIBRATING); + if (guiderType == GUIDE_INTERNAL) + { + if (Options::guideDarkFrameEnabled()) + operationStack.push(GUIDE_DARK); - 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&))); + // 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); - guider = internalGuider; + // Do not subframe and auto-select star on Image Guiding mode + if (Options::imageGuidingEnabled() == false) + { + operationStack.push(GUIDE_SUBFRAME); + operationStack.push(GUIDE_STAR_SELECT); + } - internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); - internalGuider->setRegionAxis(opsGuide->kcfg_GuideRegionAxis->currentText().toInt()); + operationStack.push(GUIDE_CAPTURE); - clearCalibrationB->setEnabled(true); - guideB->setEnabled(true); - captureB->setEnabled(true); - loopB->setEnabled(true); - darkFrameCheck->setEnabled(true); - subFrameCheck->setEnabled(true); + // 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); - guiderCombo->setEnabled(true); - ST4Combo->setEnabled(true); - exposureIN->setEnabled(true); - binningCombo->setEnabled(true); - boxSizeCombo->setEnabled(true); - filterCombo->setEnabled(true); + // Subframe if required + operationStack.push(GUIDE_SUBFRAME); - 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); + // First capture an image + operationStack.push(GUIDE_CAPTURE); + } - guiderCombo->setToolTip(i18n("Select guide camera.")); + } + break; - updateGuideParams(); - } - break; + default: + break; + } +} - case GUIDE_PHD2: - if (phd2Guider.isNull()) - phd2Guider = new PHD2(); +bool Guide::executeOperationStack() +{ + if (operationStack.isEmpty()) + return false; - guider = phd2Guider; - phd2Guider->setGuideView(guideView); + GuideState nextOperation = operationStack.pop(); - connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap&)), this, SIGNAL(newStarPixmap(QPixmap&))); + bool actionRequired = false; - clearCalibrationB->setEnabled(true); - captureB->setEnabled(false); - loopB->setEnabled(false); - darkFrameCheck->setEnabled(false); - subFrameCheck->setEnabled(false); - guideB->setEnabled(false); //This will be enabled later when equipment connects (or not) - externalConnectB->setEnabled(false); + switch (nextOperation) + { + case GUIDE_SUBFRAME: + actionRequired = executeOneOperation(nextOperation); + break; - 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); - - if (Options::guideRemoteImagesEnabled() == false) - { - //guiderCombo->setCurrentIndex(-1); - guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); - } - else - 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(); + case GUIDE_DARK: + actionRequired = executeOneOperation(nextOperation); break; - case GUIDE_LINGUIDER: - if (linGuider.isNull()) - linGuider = new LinGuider(); + case GUIDE_CAPTURE: + actionRequired = captureOneFrame(); + break; - guider = linGuider; + case GUIDE_STAR_SELECT: + actionRequired = executeOneOperation(nextOperation); + break; - clearCalibrationB->setEnabled(true); - captureB->setEnabled(false); - loopB->setEnabled(false); - darkFrameCheck->setEnabled(false); - subFrameCheck->setEnabled(false); - guideB->setEnabled(true); - externalConnectB->setEnabled(true); + case GUIDE_CALIBRATING: + if (guiderType == GUIDE_INTERNAL) + { + guider->setStarPosition(starCenter); + dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); - controlGroup->setEnabled(false); - infoGroup->setEnabled(false); - driftGraphicsGroup->setEnabled(false); + // No need to calibrate + if (Options::imageGuidingEnabled()) + { + setStatus(GUIDE_CALIBRATION_SUCESS); + break; + } - ST4Combo->setEnabled(false); - exposureIN->setEnabled(false); - binningCombo->setEnabled(false); - boxSizeCombo->setEnabled(false); - filterCombo->setEnabled(false); + // Tracking must be engaged + if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) + currentTelescope->setTrackEnabled(true); + } - if (Options::guideRemoteImagesEnabled() == false) + if (guider->calibrate()) { - guiderCombo->setCurrentIndex(-1); - guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); + if (guiderType == GUIDE_INTERNAL) + disconnect(guideView, SIGNAL(trackingStarSelected(int, int)), this, + SLOT(setTrackingStar(int, int))); + setBusy(true); } else - 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); - } - - 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); + { + emit newStatus(GUIDE_CALIBRATION_ERROR); + state = GUIDE_IDLE; + appendLogText(i18n("Calibration failed to start.")); + setBusy(false); + } 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::onInputParamChanged() -{ - QSpinBox *pSB; - QDoubleSpinBox *pDSB; - - QObject *obj = sender(); - - if ((pSB = dynamic_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 = dynamic_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()); - } -} - -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); -} - -#if 0 -void Guide::onRapidGuideChanged(bool enable) -{ - if (m_isStarted) - { - guideModule->appendLogText(i18n("You must stop auto guiding before changing this setting.")); - return; - } - - m_useRapidGuide = enable; - - if (m_useRapidGuide) - { - guideModule->appendLogText(i18n("Rapid Guiding is enabled. Guide star will be determined automatically by the CCD driver. No frames are sent to Ekos unless explicitly enabled by the user in the CCD driver settings.")); - } - else - guideModule->appendLogText(i18n("Rapid Guiding is disabled.")); -} -#endif - -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()); -} - -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 (state == GUIDE_STAR_SELECT) - { - guider->setStarPosition(newStarPosition); - guider->calibrate(); - }*/ + break; + } - if (operationStack.isEmpty() == false) - executeOperationStack(); + // If an additional action is required, return return and continue later + if (actionRequired) + return true; + // Otherwise, continue processing the stack + else + return executeOperationStack(); } -void Guide::setAxisDelta(double ra, double de) +bool Guide::executeOneOperation(GuideState operation) { - // Time since timer started. - double key = guideTimer.elapsed() / 1000.0; + bool actionRequired = false; - ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - driftGraph->graph(0)->addData(key, ra); - driftGraph->graph(1)->addData(key, de); + int subBinX, subBinY; + targetChip->getBinning(&subBinX, &subBinY); - int currentNumPoints=driftGraph->graph(0)->dataCount(); - guideSlider->setMaximum(currentNumPoints); - if(graphOnLatestPt) - guideSlider->setValue(currentNumPoints); + 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); - // Expand range if it doesn't fit already - if (driftGraph->yAxis->range().contains(ra) == false) - driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); + int offset = boxSizeCombo->currentText().toInt() / subBinX; - if (driftGraph->yAxis->range().contains(de) == false) - driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); + int x = starCenter.x(); + int y = starCenter.y(); - // 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(); + x = (x - offset * 2) * subBinX; + y = (y - offset * 2) * subBinY; + int w = offset * 4 * subBinX; + int h = offset * 4 * subBinY; - //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 (x < minX) + x = minX; + if (y < minY) + y = minY; + if ((x + w) > maxW) + w = maxW - x; + if ((y + h) > maxH) + h = maxH - y; - 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();}); - } + targetChip->setFrame(x, y, w, h); - driftPlot->replot(); + subFramed = true; + QVariantMap settings = frameSettings[targetChip]; + settings["x"] = x; + settings["y"] = y; + settings["w"] = w; + settings["h"] = h; + settings["binx"] = subBinX; + settings["biny"] = subBinY; - l_DeltaRA->setText(QString::number(ra, 'f', 2)); - l_DeltaDEC->setText(QString::number(de, 'f', 2)); + frameSettings[targetChip] = settings; - emit newAxisDelta(ra, de); + 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(); - profilePixmap = driftGraph->grab(); - emit newProfilePixmap(profilePixmap); -} + int x, y, w, h; + targetChip->getFrame(&x, &y, &w, &h); -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(sqrt(ra*ra+de*de), 'f', 2)); - emit newAxisSigma(ra, de); -} + QVariantMap settings; + settings["x"] = x; + settings["y"] = y; + settings["w"] = w; + settings["h"] = h; + settings["binx"] = 1; + settings["biny"] = 1; + frameSettings[targetChip] = settings; -QList Guide::axisDelta() -{ - QList delta; + subFramed = false; - delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); + starCenter.setX(w / (2 * subBinX)); + starCenter.setY(h / (2 * subBinX)); - return delta; -} + //starCenter.setX(0); + //starCenter.setY(0); + } + } + break; -QList Guide::axisSigma() -{ - QList sigma; + case GUIDE_DARK: + { + // Do we need to take a dark frame? + if (Options::guideDarkFrameEnabled()) + { + FITSData *darkData = nullptr; + QVariantMap settings = frameSettings[targetChip]; + uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); + uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); - sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); + darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); - return sigma; -} + connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, &Ekos::Guide::setCaptureComplete); + connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Guide::appendLogText); -void Guide::setAxisPulse(double ra, double de) -{ - l_PulseRA->setText(QString::number(static_cast(ra))); - l_PulseDEC->setText(QString::number(static_cast(de))); + actionRequired = true; - double key = guideTimer.elapsed() / 1000.0; + targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); - driftGraph->graph(4)->addData(key, ra); - driftGraph->graph(5)->addData(key, de); -} + if (darkData) + DarkLibrary::Instance()->subtract(darkData, guideView, targetChip->getCaptureFilter(), offsetX, + offsetY); + else + { + bool rc = DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), + offsetX, offsetY); + setDarkFrameEnabled(rc); + } + } + } + break; -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)) + case GUIDE_STAR_SELECT: { - 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)); + state = GUIDE_STAR_SELECT; + emit newStatus(state); - QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); - dePulseColor.setAlpha(75); - driftGraph->graph(5)->setPen(QPen(dePulseColor)); - driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); + 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::driftMouseClicked(QMouseEvent *event) +void Guide::processGuideOptions() { - if (event->buttons() & Qt::RightButton) + if (Options::guiderType() != guiderType) { - driftGraph->yAxis->setRange(-3, 3); + guiderType = static_cast(Options::guiderType()); + setGuiderType(Options::guiderType()); } } -void Guide::driftMouseOverLine(QMouseEvent *event) +void Guide::showFITSViewer() { - 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))); - } + FITSData *data = guideView->getImageData(); + if (data) + { + QUrl url = QUrl::fromLocalFile(data->filename()); + + if (fv.isNull()) + { + if (Options::singleWindowCapturedFITS()) + fv = KStars::Instance()->genericFITSViewer(); 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. + 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 - QToolTip::hideText(); + fv->updateFITS(url, 0); - driftGraph->replot(); + fv->show(); } } -void Guide::buildOperationStack(GuideState operation) +void Guide::setBLOBEnabled(bool enable, const QString &ccd) { - operationStack.clear(); + // Nothing to do if guider is international or remote images are enabled + if (guiderType == GUIDE_INTERNAL || Options::guideRemoteImagesEnabled()) + return; - switch (operation) + // If guider is external and remote images option is disabled AND BLOB is enabled, then we disabled it + + foreach(ISD::CCD *oneCCD, CCDs) { - case GUIDE_CAPTURE: - if (Options::guideDarkFrameEnabled()) - operationStack.push(GUIDE_DARK); + // If it's not the desired CCD, continue. + if (ccd.isEmpty() == false && QString(oneCCD->getDeviceName()) != ccd) + continue; - operationStack.push(GUIDE_CAPTURE); - operationStack.push(GUIDE_SUBFRAME); - break; + if (enable == false && oneCCD->isBLOBEnabled()) + { + appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->setBLOBEnabled(enable); + } + // Re-enable BLOB reception if it was disabled before when using external guiders + else if (enable && oneCCD->isBLOBEnabled() == false) + { + appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->setBLOBEnabled(enable); + } + } +} - case GUIDE_CALIBRATING: - operationStack.push(GUIDE_CALIBRATING); - if (guiderType == GUIDE_INTERNAL) - { - if (Options::guideDarkFrameEnabled()) - operationStack.push(GUIDE_DARK); +void Guide::ditherDirectly() +{ + double ditherPulse = Options::ditherNoGuidingPulse(); - // 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); + // 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; - // Do not subframe and auto-select star on Image Guiding mode - if (Options::imageGuidingEnabled() == false) - { - operationStack.push(GUIDE_SUBFRAME); - operationStack.push(GUIDE_STAR_SELECT); - } + int de_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); + int de_polarity = (rand() % 2 == 0) ? 1 : -1; - operationStack.push(GUIDE_CAPTURE); + 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; - // 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); + 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); - // Subframe if required - operationStack.push(GUIDE_SUBFRAME); + 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; - // First capture an image - operationStack.push(GUIDE_CAPTURE); - } + focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; + aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; - } - break; + Options::setGuideScopeType(index); - default: - break; - } + syncTelescopeInfo(); } -bool Guide::executeOperationStack() +void Guide::setDefaultST4(const QString &driver) { - if (operationStack.isEmpty()) - return false; + Options::setDefaultST4Driver(driver); +} - GuideState nextOperation = operationStack.pop(); +void Guide::setDefaultCCD(const QString &ccd) +{ + if (guiderType == GUIDE_INTERNAL) + Options::setDefaultGuideCCD(ccd); + else if (ccd.isEmpty() == false) + { + QString ccdName = ccd; + ccdName = ccdName.remove(" Guider"); + setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); + } +} - bool actionRequired = false; +void Guide::handleManualDither() +{ + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (targetChip == nullptr) + return; - switch (nextOperation) + Ui::ManualDither ditherDialog; + QDialog container(this); + ditherDialog.setupUi(&container); + + if (guiderType != GUIDE_INTERNAL) { - case GUIDE_SUBFRAME: - actionRequired = executeOneOperation(nextOperation); - break; + ditherDialog.coordinatesR->setEnabled(false); + ditherDialog.x->setEnabled(false); + ditherDialog.y->setEnabled(false); + } - case GUIDE_DARK: - actionRequired = executeOneOperation(nextOperation); - break; + int minX, maxX, minY, maxY, minW, maxW, minH, maxH; + targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); - case GUIDE_CAPTURE: - actionRequired = captureOneFrame(); - break; + ditherDialog.x->setMinimum(minX); + ditherDialog.x->setMaximum(maxX); + ditherDialog.y->setMinimum(minY); + ditherDialog.y->setMaximum(maxY); - case GUIDE_STAR_SELECT: - actionRequired = executeOneOperation(nextOperation); - break; + ditherDialog.x->setValue(starCenter.x()); + ditherDialog.y->setValue(starCenter.y()); - case GUIDE_CALIBRATING: - if (guiderType == GUIDE_INTERNAL) - { - guider->setStarPosition(starCenter); - dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); + 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()); + } + } +} - // No need to calibrate - if (Options::imageGuidingEnabled()) - { - setStatus(GUIDE_CALIBRATION_SUCESS); - break; - } +bool Guide::connectGuider() +{ + return guider->Connect(); +} - // Tracking must be engaged - if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) - currentTelescope->setTrackEnabled(true); - } +bool Guide::disconnectGuider() +{ + return guider->Disconnect(); +} + +void Guide::initPlots() +{ + // 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)")); - 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; + //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); - default: - break; - } + //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); - // If an additional action is required, return return and continue later - if (actionRequired) - return true; - // Otherwise, continue processing the stack - else - return executeOperationStack(); -} + // 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); -bool Guide::executeOneOperation(GuideState operation) -{ - bool actionRequired = false; + // 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); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + // 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)); - int subBinX, subBinY; - targetChip->getBinning(&subBinX, &subBinY); + // 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)); - 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); + // 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); - int offset = boxSizeCombo->currentText().toInt() / subBinX; + // 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); - int x = starCenter.x(); - int y = starCenter.y(); + //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); - x = (x - offset * 2) * subBinX; - y = (y - offset * 2) * subBinY; - int w = offset * 4 * subBinX; - int h = offset * 4 * subBinY; + connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); + connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); - 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); + //drift plot + double accuracyRadius = 2; - subFramed = true; - QVariantMap settings = frameSettings[targetChip]; - settings["x"] = x; - settings["y"] = y; - settings["w"] = w; - settings["h"] = h; - settings["binx"] = subBinX; - settings["biny"] = subBinY; + driftPlot->setBackground(QBrush(Qt::black)); + driftPlot->setSelectionTolerance(10); - frameSettings[targetChip] = settings; + driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); + driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); - 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(); + driftPlot->xAxis->setTickPen(QPen(Qt::white, 1)); + driftPlot->yAxis->setTickPen(QPen(Qt::white, 1)); - int x, y, w, h; - targetChip->getFrame(&x, &y, &w, &h); + driftPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); + driftPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); - QVariantMap settings; - settings["x"] = x; - settings["y"] = y; - settings["w"] = w; - settings["h"] = h; - settings["binx"] = 1; - settings["biny"] = 1; - frameSettings[targetChip] = settings; + driftPlot->xAxis->setTickLabelColor(Qt::white); + driftPlot->yAxis->setTickLabelColor(Qt::white); - subFramed = false; + driftPlot->xAxis->setLabelColor(Qt::white); + driftPlot->yAxis->setLabelColor(Qt::white); - starCenter.setX(w / (2 * subBinX)); - starCenter.setY(h / (2 * subBinX)); + 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)); - //starCenter.setX(0); - //starCenter.setY(0); - } - } - break; + driftPlot->xAxis->setLabelPadding(2); + driftPlot->yAxis->setLabelPadding(2); - case GUIDE_DARK: - { - // Do we need to take a dark frame? - if (Options::guideDarkFrameEnabled()) - { - FITSData *darkData = nullptr; - QVariantMap settings = frameSettings[targetChip]; - uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); - uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); + 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)); - darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); + driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); + driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); - connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, &Ekos::Guide::setCaptureComplete); - connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Guide::appendLogText); + driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - actionRequired = true; + driftPlot->setInteractions(QCP::iRangeZoom); + driftPlot->setInteraction(QCP::iRangeDrag, true); - targetChip->setCaptureFilter((FITSScale)filterCombo->currentIndex()); + driftPlot->addGraph(); + driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); + driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); - if (darkData) - DarkLibrary::Instance()->subtract(darkData, guideView, targetChip->getCaptureFilter(), offsetX, - offsetY); - else - { - bool rc = DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), - offsetX, offsetY); - setDarkFrameEnabled(rc); - } - } - } - break; + driftPlot->addGraph(); + driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); + driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); - case GUIDE_STAR_SELECT: - { - state = GUIDE_STAR_SELECT; - emit newStatus(state); + connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); + connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); - 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; + //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()); - default: - break; - } + //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(); - return actionRequired; -} + driftPlot->resize(190, 190); + driftPlot->replot(); -void Guide::processGuideOptions() -{ - if (Options::guiderType() != guiderType) - { - guiderType = static_cast(Options::guiderType()); - setGuiderType(Options::guiderType()); - } + buildTarget(); } -void Guide::showFITSViewer() +void Guide::initView() { - 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(); - } + 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::setBLOBEnabled(bool enable, const QString &ccd) +void Guide::initConnections() { - // Nothing to do if guider is international or remote images are enabled - if (guiderType == GUIDE_INTERNAL || Options::guideRemoteImagesEnabled()) - return; + // Exposure Timeout + captureTimeout.setSingleShot(true); + connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout); - // If guider is external and remote images option is disabled AND BLOB is enabled, then we disabled it + // Guiding Box Size + connect(boxSizeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTrackingBoxSize); - foreach(ISD::CCD *oneCCD, CCDs) + // Guider CCD Selection + connect(guiderCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::setDefaultCCD); + connect(guiderCombo, static_cast(&QComboBox::activated), this, + [&](int index) { - // If it's not the desired CCD, continue. - if (ccd.isEmpty() == false && QString(oneCCD->getDeviceName()) != ccd) - continue; - - if (enable == false && oneCCD->isBLOBEnabled()) + if (guiderType == GUIDE_INTERNAL) { - appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->setBLOBEnabled(enable); + starCenter = QVector3D(); + checkCCD(index); } - // Re-enable BLOB reception if it was disabled before when using external guiders - else if (enable && oneCCD->isBLOBEnabled() == false) + else if (index >= 0) { - appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->setBLOBEnabled(enable); + // Disable or enable selected CCD based on options + QString ccdName = guiderCombo->currentText().remove(" Guider"); + setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); + checkCCD(index); } } -} + ); -void Guide::ditherDirectly() -{ - double ditherPulse = Options::ditherNoGuidingPulse(); + FOVScopeCombo->setCurrentIndex(Options::guideScopeType()); + connect(FOVScopeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTelescopeType); - // 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(((double)rand() / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); - int ra_polarity = (rand() % 2 == 0) ? 1 : -1; + // Dark Frame Check + connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); + // Subframe check + connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); + // ST4 Selection + connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString & text) + { + setDefaultST4(text); + setST4(text); + }); - int de_msec = static_cast(((double)rand() / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); - int de_polarity = (rand() % 2 == 0) ? 1 : -1; + // Binning Combo Selection + connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateCCDBin); - 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; + // RA/DEC Enable directions + connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); + connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); - 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); + // 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); - 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; - } -} + // Auto star check + connect(autoStarCheck, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); -void Guide::updateTelescopeType(int index) -{ - if (currentCCD == nullptr) - return; + // Declination Swap + connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); - focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; - aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; + // PID Control - Proportional Gain + connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); - Options::setGuideScopeType(index); + // PID Control - Integral Gain + connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); - syncTelescopeInfo(); -} + // PID Control - Derivative Gain + connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); -void Guide::setDefaultST4(const QString &driver) -{ - Options::setDefaultST4Driver(driver); -} + // Max Pulse Duration (ms) + connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); -void Guide::setDefaultCCD(const QString &ccd) -{ - if (guiderType == GUIDE_INTERNAL) - Options::setDefaultGuideCCD(ccd); - else if (ccd.isEmpty() == false) - { - QString ccdName = ccd; - ccdName = ccdName.remove(" Guider"); - setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); - } -} + // Min Pulse Duration (ms) + connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); -void Guide::handleManualDither() -{ - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (targetChip == nullptr) - return; + // Capture + connect(captureB, &QPushButton::clicked, this, [this]() + { + state = GUIDE_CAPTURE; + emit newStatus(state); - Ui::ManualDither ditherDialog; - QDialog container(this); - ditherDialog.setupUi(&container); + capture(); + }); - if (guiderType != GUIDE_INTERNAL) + connect(loopB, &QPushButton::clicked, this, [this]() { - ditherDialog.coordinatesR->setEnabled(false); - ditherDialog.x->setEnabled(false); - ditherDialog.y->setEnabled(false); - } + state = GUIDE_LOOPING; + emit newStatus(state); - int minX, maxX, minY, maxY, minW, maxW, minH, maxH; - targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); + capture(); + }); - ditherDialog.x->setMinimum(minX); - ditherDialog.x->setMaximum(maxX); - ditherDialog.y->setMinimum(minY); - ditherDialog.y->setMaximum(maxY); + // Stop + connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); - ditherDialog.x->setValue(starCenter.x()); - ditherDialog.y->setValue(starCenter.y()); + // Clear Calibrate + //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); + connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); - if (container.exec() == QDialog::Accepted) + // Guide + connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); + + // Connect External Guide + connect(externalConnectB, &QPushButton::clicked, this, [&]() { - if (ditherDialog.magnitudeR->isChecked()) - guider->dither(ditherDialog.magnitude->value()); - else - { - dynamic_cast(guider)->ditherXY(ditherDialog.x->value(), ditherDialog.y->value()); - } - } -} + setBLOBEnabled(false); + guider->Connect(); + }); + connect(externalDisconnectB, &QPushButton::clicked, this, [&]() + { + setBLOBEnabled(true); + guider->Disconnect(); + }); -bool Guide::connectGuider() -{ - return guider->Connect(); -} + // Pulse Timer + pulseTimer.setSingleShot(true); + connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); -bool Guide::disconnectGuider() -{ - return guider->Disconnect(); + //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 + connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); } } diff --git a/kstars/ekos/guide/guide.ui b/kstars/ekos/guide/guide.ui --- a/kstars/ekos/guide/guide.ui +++ b/kstars/ekos/guide/guide.ui @@ -6,8 +6,8 @@ 0 0 - 730 - 529 + 736 + 568 @@ -86,26 +86,6 @@
- - - - <html><head/><body><p>Subframe the image around the guide star. Before checking this option, you must <span style=" font-weight:600;">first</span> capture an image and select a guide star. Uncheck it to take a full frame again.</p></body></html> - - - Subframe - - - - - - - <html><head/><body><p>Subtract dark frame. If no dark frame is available, a new dark frame shall be captured and saved for future use.</p></body></html> - - - Dark - - - @@ -145,32 +125,6 @@ - - - - false - - - Guide - - - true - - - true - - - - - - - false - - - Stop - - - @@ -195,7 +149,7 @@ - + .. @@ -206,6 +160,68 @@ + + + + false + + + Stop + + + + + + + <html><head/><body><p>Subframe the image around the guide star. Before checking this option, you must <span style=" font-weight:600;">first</span> capture an image and select a guide star. Uncheck it to take a full frame again.</p></body></html> + + + Subframe + + + + + + + <html><head/><body><p>Subtract dark frame. If no dark frame is available, a new dark frame shall be captured and saved for future use.</p></body></html> + + + Dark + + + + + + + Automatically select the calibration star. + + + Auto Star + + + + + + + false + + + + 0 + 0 + + + + Guide + + + true + + + true + + +
@@ -533,14 +549,46 @@ - Mount guiding rate. Find out the guiding rate used by your mount and update the value here to get the <b>recommended</b> value of proportional gain suitable for your mount. Setting this value <b>does not</b> change your mount guiding rate. + Guiding rate + + + true + + + + 24 + 24 + + + + Mount guiding rate. Find out the guiding rate used by your mount and update the value here to get the <b>recommended</b> value of proportional gain suitable for your mount. Setting this value <b>does not</b> change your mount guiding rate. + + + + + + + .. + + + + 24 + 24 + + + + true + + + + Mount guiding rate (x15"/sec) @@ -1597,23 +1645,21 @@ - NonLinearDoubleSpinBox - QDoubleSpinBox -
auxiliary/nonlineardoublespinbox.h
-
- QCustomPlot QWidget
auxiliary/qcustomplot.h
1
+ + NonLinearDoubleSpinBox + QDoubleSpinBox +
auxiliary/nonlineardoublespinbox.h
+
captureB loopB showFITSViewerB - subFrameCheck - darkFrameCheck guideB clearCalibrationB stopB diff --git a/kstars/ekos/guide/internalguide/internalguider.h b/kstars/ekos/guide/internalguide/internalguider.h --- a/kstars/ekos/guide/internalguide/internalguider.h +++ b/kstars/ekos/guide/internalguide/internalguider.h @@ -169,6 +169,7 @@ int ra_total_pulse { 0 }; int de_total_pulse { 0 }; double phi { 0 }; + uint8_t backlash { 0 }; } m_CalibrationParams; struct diff --git a/kstars/ekos/guide/internalguide/internalguider.cpp b/kstars/ekos/guide/internalguide/internalguider.cpp --- a/kstars/ekos/guide/internalguide/internalguider.cpp +++ b/kstars/ekos/guide/internalguide/internalguider.cpp @@ -531,6 +531,7 @@ 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++; @@ -604,13 +605,19 @@ // 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) { - // Increase pulse by 200% - m_CalibrationParams.last_pulse = Options::calibrationPulseDuration()*2; + 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; diff --git a/kstars/ekos/guide/opscalibration.ui b/kstars/ekos/guide/opscalibration.ui --- a/kstars/ekos/guide/opscalibration.ui +++ b/kstars/ekos/guide/opscalibration.ui @@ -105,19 +105,6 @@
- - - Automatically select the calibration star. - - - Auto Star - - - false - - - - false diff --git a/kstars/ekos/manager.h b/kstars/ekos/manager.h --- a/kstars/ekos/manager.h +++ b/kstars/ekos/manager.h @@ -163,6 +163,9 @@ */ void addNamedProfile(const QJsonObject &profileInfo); + /** Same as above, except it edits an existing named profile */ + void editNamedProfile(const QJsonObject &profileInfo); + /** * @brief deleteProfile Delete existing equipment profile * @param name Name of profile diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -2333,6 +2333,16 @@ 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); diff --git a/kstars/ekos/mount/mount.h b/kstars/ekos/mount/mount.h --- a/kstars/ekos/mount/mount.h +++ b/kstars/ekos/mount/mount.h @@ -245,7 +245,7 @@ Q_INVOKABLE void centerMount(); // Get list of scopes - QJsonArray getScopes() const; + //QJsonArray getScopes() const; /* * @brief Check if a meridian flip if necessary. diff --git a/kstars/ekos/mount/mount.cpp b/kstars/ekos/mount/mount.cpp --- a/kstars/ekos/mount/mount.cpp +++ b/kstars/ekos/mount/mount.cpp @@ -601,6 +601,10 @@ void Mount::meridianFlipSetupChanged() { + if (meridianFlipCheckBox->isChecked() == false) + // reset meridian flip + setMeridianFlipStatus(FLIP_NONE); + emit newMeridianFlipSetup(meridianFlipCheckBox->isChecked(), meridianFlipTimeBox->value()); } @@ -1377,34 +1381,34 @@ return currentTelescope->getSlewRate(); } -QJsonArray Mount::getScopes() const -{ - QJsonArray scopes; - if (currentTelescope == nullptr) - return scopes; - - QJsonObject primary = - { - {"name", "Primary"}, - {"mount", currentTelescope->getDeviceName()}, - {"aperture", primaryScopeApertureIN->value()}, - {"focalLength", primaryScopeFocalIN->value()}, - }; - - scopes.append(primary); - - QJsonObject guide = - { - {"name", "Guide"}, - {"mount", currentTelescope->getDeviceName()}, - {"aperture", primaryScopeApertureIN->value()}, - {"focalLength", primaryScopeFocalIN->value()}, - }; - - scopes.append(guide); - - return scopes; -} +//QJsonArray Mount::getScopes() const +//{ +// QJsonArray scopes; +// if (currentTelescope == nullptr) +// return scopes; + +// QJsonObject primary = +// { +// {"name", "Primary"}, +// {"mount", currentTelescope->getDeviceName()}, +// {"aperture", primaryScopeApertureIN->value()}, +// {"focalLength", primaryScopeFocalIN->value()}, +// }; + +// scopes.append(primary); + +// QJsonObject guide = +// { +// {"name", "Guide"}, +// {"mount", currentTelescope->getDeviceName()}, +// {"aperture", primaryScopeApertureIN->value()}, +// {"focalLength", primaryScopeFocalIN->value()}, +// }; + +// scopes.append(guide); + +// return scopes; +//} void Mount::startParkTimer() { diff --git a/kstars/ekos/profileeditor.cpp b/kstars/ekos/profileeditor.cpp --- a/kstars/ekos/profileeditor.cpp +++ b/kstars/ekos/profileeditor.cpp @@ -951,26 +951,42 @@ int primaryID = profile["primary_scope"].toInt(-1); int guideID = profile["guide_scope"].toInt(-1); - for (int i = 1; i < ui->primaryScopeCombo->count(); i++) + if (primaryID <= 0) + ui->primaryScopeCombo->setCurrentIndex(0); + else { - if (m_scopeList[i - 1]->id().toInt() == primaryID) + for (int i = 1; i < ui->primaryScopeCombo->count(); i++) { - ui->primaryScopeCombo->setCurrentIndex(i); - break; + if (m_scopeList[i - 1]->id().toInt() == primaryID) + { + ui->primaryScopeCombo->setCurrentIndex(i); + break; + } } } - for (int i = 1; i < ui->guideScopeCombo->count(); i++) + if (guideID <= 0) + ui->guideScopeCombo->setCurrentIndex(0); + else { - if (m_scopeList[i - 1]->id().toInt() == guideID) + for (int i = 1; i < ui->guideScopeCombo->count(); i++) { - ui->guideScopeCombo->setCurrentIndex(i); - break; + if (m_scopeList[i - 1]->id().toInt() == guideID) + { + ui->guideScopeCombo->setCurrentIndex(i); + break; + } } } // Drivers - ui->mountCombo->setCurrentText(profile["mount"].toString("--")); + const QString mount = profile["mount"].toString("--"); + if (mount != "--") + { + ui->mountCombo->addItem(mount); + ui->mountCombo->setCurrentIndex(ui->mountCombo->count() - 1); + } + ui->ccdCombo->setCurrentText(profile["ccd"].toString("--")); ui->guiderCombo->setCurrentText(profile["guider"].toString("--")); ui->focuserCombo->setCurrentText(profile["focuser"].toString("--")); diff --git a/kstars/ekos/scheduler/mosaic.cpp b/kstars/ekos/scheduler/mosaic.cpp --- a/kstars/ekos/scheduler/mosaic.cpp +++ b/kstars/ekos/scheduler/mosaic.cpp @@ -220,7 +220,7 @@ void Mosaic::setCenter(const SkyPoint &value) { center = value; - center.apparentCoord((long double)J2000, KStars::Instance()->data()->ut().djd()); + center.apparentCoord(static_cast(J2000), KStars::Instance()->data()->ut().djd()); } void Mosaic::setCameraSize(uint16_t width, uint16_t height) @@ -243,7 +243,7 @@ void Mosaic::calculateFOV() { if (cameraWSpin->value() == 0 || cameraHSpin->value() == 0 || pixelWSizeSpin->value() == 0 || - pixelHSizeSpin->value() == 0 || focalLenSpin->value() == 0) + pixelHSizeSpin->value() == 0 || focalLenSpin->value() == 0) return; fovGroup->setEnabled(true); @@ -324,8 +324,8 @@ scene.setSceneRect(skyMapItem->boundingRect()); // Center tile - mosaicTileItem->setPos(skyMapItem->mapToScene(QPointF( mosaicWSpin->value()*cameraWFOVSpin->value()*pixelsPerArcmin/2, - mosaicHSpin->value()*cameraHFOVSpin->value()*pixelsPerArcmin/2))); + mosaicTileItem->setPos(skyMapItem->mapToScene(QPointF( mosaicWSpin->value()*cameraWFOVSpin->value()*pixelsPerArcmin / 2, + mosaicHSpin->value()*cameraHFOVSpin->value()*pixelsPerArcmin / 2))); } void Mosaic::render() @@ -386,7 +386,7 @@ createJobsB->setEnabled(true); if (mosaicTileItem->getWidth() != mosaicWSpin->value() || mosaicTileItem->getHeight() != mosaicHSpin->value() || - mosaicTileItem->getOverlap() != overlapSpin->value() || mosaicTileItem->getPA() != rotationSpin->value()) + mosaicTileItem->getOverlap() != overlapSpin->value() || mosaicTileItem->getPA() != rotationSpin->value()) { if (mosaicTileItem->getPA() != rotationSpin->value()) Options::setCameraRotation(rotationSpin->value()); @@ -398,7 +398,7 @@ updateTargetFOV(); qCDebug(KSTARS_EKOS_SCHEDULER) << "Tile FOV in pixels W:" << cameraWFOVSpin->value() * pixelsPerArcmin << "H:" - << cameraHFOVSpin->value() * pixelsPerArcmin; + << cameraHFOVSpin->value() * pixelsPerArcmin; mosaicTileItem->setDimension(mosaicWSpin->value(), mosaicHSpin->value()); mosaicTileItem->setPA(rotationSpin->value()); @@ -430,7 +430,7 @@ qCDebug(KSTARS_EKOS_SCHEDULER) << "#2 Center RA:" << center.ra0().toHMSString() << "DE:" << center.dec0().toDMSString(); - center.apparentCoord((long double)J2000, KStars::Instance()->data()->ut().djd()); + center.apparentCoord(static_castJ2000, KStars::Instance()->data()->ut().djd()); KStars::Instance()->setApproxFOV(targetWFOVSpin->value() * 2 / 60.0); diff --git a/kstars/ekos/scheduler/scheduler.h b/kstars/ekos/scheduler/scheduler.h --- a/kstars/ekos/scheduler/scheduler.h +++ b/kstars/ekos/scheduler/scheduler.h @@ -312,11 +312,23 @@ void setJobManipulation(bool can_reorder, bool can_delete); /** + * @brief set all GUI fields to the values of the given scheduler job + */ + void syncGUIToJob(SchedulerJob *job); + + /** * @brief jobSelectionChanged Update UI state when the job list is clicked once. */ void clickQueueTable(QModelIndex index); /** + * @brief Update scheduler parameters to the currently selected scheduler job + * @param current table position + * @param previous table position + */ + void queueTableSelectionChanged(QModelIndex current, QModelIndex previous); + + /** * @brief reorderJobs Change the order of jobs in the UI based on a subset of its jobs. */ bool reorderJobs(QList reordered_sublist); @@ -727,6 +739,5 @@ static const uint32_t FOCUS_INACTIVITY_TIMEOUT = 120000; static const uint32_t CAPTURE_INACTIVITY_TIMEOUT = 120000; static const uint16_t GUIDE_INACTIVITY_TIMEOUT = 60000; - }; } diff --git a/kstars/ekos/scheduler/scheduler.cpp b/kstars/ekos/scheduler/scheduler.cpp --- a/kstars/ekos/scheduler/scheduler.cpp +++ b/kstars/ekos/scheduler/scheduler.cpp @@ -169,6 +169,7 @@ connect(queueDownB, &QPushButton::clicked, this, &Scheduler::moveJobDown); connect(evaluateOnlyB, &QPushButton::clicked, this, &Scheduler::startJobEvaluation); connect(sortJobsB, &QPushButton::clicked, this, &Scheduler::sortJobsPerAltitude); + connect(queueTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Scheduler::queueTableSelectionChanged); connect(queueTable, &QAbstractItemView::clicked, this, &Scheduler::clickQueueTable); connect(queueTable, &QAbstractItemView::doubleClicked, this, &Scheduler::loadJob); @@ -454,7 +455,7 @@ void Scheduler::saveJob() { - if (state == SCHEDULER_RUNNIG) + if (state == SCHEDULER_RUNNING) { appendLogText(i18n("Warning: You cannot add or modify a job while the scheduler is running.")); return; @@ -726,47 +727,21 @@ } } -void Scheduler::loadJob(QModelIndex i) +void Scheduler::syncGUIToJob(SchedulerJob *job) { - if (jobUnderEdit == i.row()) - return; - - if (state == SCHEDULER_RUNNIG) - { - appendLogText(i18n("Warning: you cannot add or modify a job while the scheduler is running.")); - return; - } - - SchedulerJob * const job = jobs.at(i.row()); - - if (job == nullptr) - return; - - watchJobChanges(false); - - //job->setState(SchedulerJob::JOB_IDLE); - //job->setStage(SchedulerJob::STAGE_IDLE); - nameEdit->setText(job->getName()); prioritySpin->setValue(job->getPriority()); raBox->showInHours(job->getTargetCoords().ra0()); decBox->showInDegrees(job->getTargetCoords().dec0()); if (job->getFITSFile().isEmpty() == false) - { fitsEdit->setText(job->getFITSFile().toLocalFile()); - fitsURL = job->getFITSFile(); - } else - { fitsEdit->clear(); - fitsURL = QUrl(); - } sequenceEdit->setText(job->getSequenceFile().toLocalFile()); - sequenceURL = job->getSequenceFile(); trackStepCheck->setChecked(job->getStepPipeline() & SchedulerJob::USE_TRACK); focusStepCheck->setChecked(job->getStepPipeline() & SchedulerJob::USE_FOCUS); @@ -841,6 +816,38 @@ break; } + setJobManipulation(!Options::sortSchedulerJobs(), true); +} + +void Scheduler::loadJob(QModelIndex i) +{ + if (jobUnderEdit == i.row()) + return; + + if (state == SCHEDULER_RUNNING) + { + appendLogText(i18n("Warning: you cannot add or modify a job while the scheduler is running.")); + return; + } + + SchedulerJob * const job = jobs.at(i.row()); + + if (job == nullptr) + return; + + watchJobChanges(false); + + //job->setState(SchedulerJob::JOB_IDLE); + //job->setStage(SchedulerJob::STAGE_IDLE); + syncGUIToJob(job); + + if (job->getFITSFile().isEmpty() == false) + fitsURL = job->getFITSFile(); + else + fitsURL = QUrl(); + + sequenceURL = job->getSequenceFile(); + /* Turn the add button into an apply button */ setJobAddApply(false); @@ -857,6 +864,26 @@ watchJobChanges(true); } +void Scheduler::queueTableSelectionChanged(QModelIndex current, QModelIndex previous) +{ + Q_UNUSED(previous); + + // prevent selection when not idle + if (state != SCHEDULER_IDLE) + return; + + if (current.row() < 0 || (current.row()+1) > jobs.size()) + return; + + SchedulerJob * const job = jobs.at(current.row()); + + if (job == nullptr) + return; + + resetJobEdit(); + syncGUIToJob(job); +} + void Scheduler::clickQueueTable(QModelIndex index) { setJobManipulation(!Options::sortSchedulerJobs() && index.isValid(), index.isValid()); @@ -882,19 +909,21 @@ void Scheduler::setJobManipulation(bool can_reorder, bool can_delete) { + bool can_edit = (state == SCHEDULER_IDLE); + if (can_reorder) { int const currentRow = queueTable->currentRow(); - queueUpB->setEnabled(0 < currentRow); - queueDownB->setEnabled(currentRow < queueTable->rowCount() - 1); + queueUpB->setEnabled(can_edit && 0 < currentRow); + queueDownB->setEnabled(can_edit && currentRow < queueTable->rowCount() - 1); } else { queueUpB->setEnabled(false); queueDownB->setEnabled(false); } - sortJobsB->setEnabled(can_reorder); - removeFromQueueB->setEnabled(can_delete); + sortJobsB->setEnabled(can_edit && can_reorder); + removeFromQueueB->setEnabled(can_edit && can_delete); } bool Scheduler::reorderJobs(QList reordered_sublist) @@ -1067,8 +1096,16 @@ startB->setEnabled(false); pauseB->setEnabled(false); } - /* Else load the settings of the job that was just deleted */ - else loadJob(queueTable->currentIndex()); + + /* Else update the selection */ + else + { + if (currentRow > queueTable->rowCount()) + currentRow = queueTable->rowCount() - 1; + + loadJob(queueTable->currentIndex()); + queueTable->selectRow(currentRow); + } /* If needed, reset edit mode to clean up UI */ if (jobUnderEdit >= 0) @@ -1085,7 +1122,7 @@ void Scheduler::toggleScheduler() { - if (state == SCHEDULER_RUNNIG) + if (state == SCHEDULER_RUNNING) { preemptiveShutdown = false; stop(); @@ -1096,7 +1133,7 @@ void Scheduler::stop() { - if (state != SCHEDULER_RUNNIG) + if (state != SCHEDULER_RUNNING) return; qCInfo(KSTARS_EKOS_SCHEDULER) << "Scheduler is stopping..."; @@ -1251,7 +1288,7 @@ /* Reset and re-evaluate all scheduler jobs, then start the Scheduler */ startJobEvaluation(); - state = SCHEDULER_RUNNIG; + state = SCHEDULER_RUNNING; emit newStatus(state); schedulerTimer.start(); @@ -1266,7 +1303,7 @@ /* Edit-related buttons are still disabled */ /* The end-user cannot update the schedule, don't re-evaluate jobs. Timer schedulerTimer is already running. */ - state = SCHEDULER_RUNNIG; + state = SCHEDULER_RUNNING; emit newStatus(state); qCDebug(KSTARS_EKOS_SCHEDULER) << "Scheduler paused."; @@ -1358,7 +1395,7 @@ case SchedulerJob::JOB_ABORTED: /* If job is aborted and we're running, keep its evaluation until there is nothing else to do */ - if (state == SCHEDULER_RUNNIG) + if (state == SCHEDULER_RUNNING) continue; /* Fall through */ case SchedulerJob::JOB_IDLE: @@ -1947,7 +1984,7 @@ /* Apply sorting to queue table, and mark it for saving if it changes */ mDirty = reorderJobs(sortedJobs) | mDirty; - if (jobEvaluationOnly || state != SCHEDULER_RUNNIG) + if (jobEvaluationOnly || state != SCHEDULER_RUNNING) { qCInfo(KSTARS_EKOS_SCHEDULER) << "Ekos finished evaluating jobs, no job selection required."; jobEvaluationOnly = false; @@ -2031,7 +2068,7 @@ } else { - if (state == SCHEDULER_RUNNIG) + if (state == SCHEDULER_RUNNING) appendLogText(i18n("Scheduler is awake. Jobs shall be started when ready...")); else appendLogText(i18n("Scheduler is awake. Jobs shall be started when scheduler is resumed.")); @@ -2203,6 +2240,9 @@ return; setCurrentJob(job); + int index = jobs.indexOf(job); + if (index >= 0) + queueTable->selectRow(index); QDateTime const now = KStarsData::Instance()->lt(); @@ -2667,7 +2707,7 @@ setCurrentJob(nullptr); - if (state == SCHEDULER_RUNNIG) + if (state == SCHEDULER_RUNNING) schedulerTimer.start(); if (preemptiveShutdown == false) @@ -3637,7 +3677,7 @@ bool Scheduler::manageConnectionLoss() { - if (SCHEDULER_RUNNIG != state) + if (SCHEDULER_RUNNING != state) return false; // Don't manage loss if Ekos is actually down in the state machine @@ -3731,11 +3771,12 @@ if (jobUnderEdit >= 0) resetJobEdit(); - qDeleteAll(jobs); - jobs.clear(); while (queueTable->rowCount() > 0) queueTable->removeRow(0); + qDeleteAll(jobs); + jobs.clear(); + LilXML *xmlParser = newLilXML(); char errmsg[MAXRBUF]; XMLEle *root = nullptr; @@ -4639,7 +4680,7 @@ if (sender() == startupProcedureButtonGroup || sender() == shutdownProcedureGroup) return; - if (0 <= jobUnderEdit && state != SCHEDULER_RUNNIG && 0 <= queueTable->currentRow()) + if (0 <= jobUnderEdit && state != SCHEDULER_RUNNING && 0 <= queueTable->currentRow()) { // Now that jobs are sorted, reset jobs that are later than the edited one for re-evaluation for (int row = jobUnderEdit; row < jobs.size(); row++) @@ -5920,7 +5961,7 @@ void Scheduler::resetAllJobs() { - if (state == SCHEDULER_RUNNIG) + if (state == SCHEDULER_RUNNING) return; // Reset capture count of all jobs before re-evaluating diff --git a/kstars/ekos/scheduler/scheduler.ui b/kstars/ekos/scheduler/scheduler.ui --- a/kstars/ekos/scheduler/scheduler.ui +++ b/kstars/ekos/scheduler/scheduler.ui @@ -1036,7 +1036,7 @@ - 15.000000000000000 + -15.000000000000000 89.900000000000006 diff --git a/kstars/ekos/scheduler/schedulerjob.h b/kstars/ekos/scheduler/schedulerjob.h --- a/kstars/ekos/scheduler/schedulerjob.h +++ b/kstars/ekos/scheduler/schedulerjob.h @@ -13,16 +13,18 @@ #include #include +#include "ksmoon.h" class QTableWidgetItem; class QLabel; +class KSMoon; class dms; class SchedulerJob { public: - SchedulerJob() = default; + SchedulerJob(); /** @brief States of a SchedulerJob. */ typedef enum { @@ -384,6 +386,53 @@ */ static bool increasingStartupTimeOrder(SchedulerJob const *a, SchedulerJob const *b); + /** + * @brief getAltitudeScore Get the altitude score of an object. The higher the better + * @param when date and time to check the target altitude, now if omitted. + * @return Altitude score. Target altitude below minimum altitude required by job or setting target under 3 degrees below minimum altitude get bad score. + */ + int16_t getAltitudeScore(QDateTime const &when = QDateTime()) const; + + /** + * @brief getMoonSeparationScore Get moon separation score. The further apart, the better, up a maximum score of 20. + * @param when date and time to check the moon separation, now if omitted. + * @return Moon separation score + */ + int16_t getMoonSeparationScore(QDateTime const &when = QDateTime()) const; + + /** + * @brief getCurrentMoonSeparation Get current moon separation in degrees at current time for the given job + * @param job scheduler job + * @return Separation in degrees + */ + double getCurrentMoonSeparation() const; + + /** + * @brief calculateAltitudeTime calculate the altitude time given the minimum altitude given. + * @param when date and time to start searching from, now if omitted. + * @return The date and time the target is at or above the argument altitude, valid if found, invalid if not achievable (always under altitude). + */ + QDateTime calculateAltitudeTime(QDateTime const &when = QDateTime()) const; + + /** + * @brief calculateCulmination find culmination time adjust for the job offset + * @param when date and time to start searching from, now if omitted + * @return The date and time the target is in entering the culmination interval, valid if found, invalid if not achievable (currently always valid). + */ + QDateTime calculateCulmination(QDateTime const &when = QDateTime()) const; + + + /** + * @brief findAltitude Find altitude given a specific time + * @param target Target + * @param when date time to find altitude + * @param is_setting whether target is setting at the argument time (optional). + * @param debug outputs calculation to log file (optional). + * @return Altitude of the target at the specific date and time given. + * @warning This function uses the current KStars geolocation. + */ + static double findAltitude(const SkyPoint &target, const QDateTime &when, bool *is_setting = nullptr, bool debug = false); + private: QString name; SkyPoint targetCoords; @@ -450,4 +499,7 @@ bool lightFramesRequired { false }; QMap capturedFramesMap; + + /// Pointer to Moon object + KSMoon *moon { nullptr }; }; diff --git a/kstars/ekos/scheduler/schedulerjob.cpp b/kstars/ekos/scheduler/schedulerjob.cpp --- a/kstars/ekos/scheduler/schedulerjob.cpp +++ b/kstars/ekos/scheduler/schedulerjob.cpp @@ -11,13 +11,24 @@ #include "dms.h" #include "kstarsdata.h" +#include "skymapcomposite.h" #include "Options.h" #include "scheduler.h" #include #include +#include + +#define BAD_SCORE -1000 +#define MIN_ALTITUDE 15.0 + +SchedulerJob::SchedulerJob() +{ + moon = dynamic_cast(KStarsData::Instance()->skyComposite()->findByName("Moon")); +} + void SchedulerJob::setName(const QString &value) { name = value; @@ -47,7 +58,7 @@ startupCondition = fileStartupCondition; // Refresh altitude - invalid date/time is taken care of when rendering - altitudeAtStartup = Ekos::Scheduler::findAltitude(targetCoords, startupTime, &isSettingAtStartup); + altitudeAtStartup = findAltitude(targetCoords, startupTime, &isSettingAtStartup); /* Refresh estimated time - which update job cells */ setEstimatedTime(estimatedTime); @@ -85,21 +96,21 @@ { setCompletionCondition(FINISH_AT); completionTime = value; - altitudeAtCompletion = Ekos::Scheduler::findAltitude(targetCoords, completionTime, &isSettingAtCompletion); + altitudeAtCompletion = findAltitude(targetCoords, completionTime, &isSettingAtCompletion); setEstimatedTime(-1); } /* If completion time is invalid, and job is looping, keep completion time undefined */ else if (FINISH_LOOP == completionCondition) { completionTime = QDateTime(); - altitudeAtCompletion = Ekos::Scheduler::findAltitude(targetCoords, completionTime, &isSettingAtCompletion); + altitudeAtCompletion = findAltitude(targetCoords, completionTime, &isSettingAtCompletion); setEstimatedTime(-1); } /* If completion time is invalid, deduce completion from startup and duration */ else if (startupTime.isValid()) { completionTime = startupTime.addSecs(estimatedTime); - altitudeAtCompletion = Ekos::Scheduler::findAltitude(targetCoords, completionTime, &isSettingAtCompletion); + altitudeAtCompletion = findAltitude(targetCoords, completionTime, &isSettingAtCompletion); updateJobCells(); } /* Else just refresh estimated time - which update job cells */ @@ -332,7 +343,7 @@ { estimatedTime = value; completionTime = startupTime.addSecs(value); - altitudeAtCompletion = Ekos::Scheduler::findAltitude(targetCoords, completionTime, &isSettingAtCompletion); + altitudeAtCompletion = findAltitude(targetCoords, completionTime, &isSettingAtCompletion); } /* Else estimated time is simply stored as is - covers FINISH_LOOP from setCompletionTime */ else estimatedTime = value; @@ -527,7 +538,7 @@ { // FIXME: Cache altitude calculations bool is_setting = false; - double const alt = Ekos::Scheduler::findAltitude(targetCoords, QDateTime(), &is_setting); + double const alt = findAltitude(targetCoords, QDateTime(), &is_setting); altitudeCell->setText(QString("%1%L2°") .arg(QChar(is_setting ? 0x2193 : 0x2191)) @@ -682,12 +693,12 @@ { bool A_is_setting = job1->isSettingAtStartup; double const altA = when.isValid() ? - Ekos::Scheduler::findAltitude(job1->getTargetCoords(), when, &A_is_setting) : + findAltitude(job1->getTargetCoords(), when, &A_is_setting) : job1->altitudeAtStartup; bool B_is_setting = job2->isSettingAtStartup; double const altB = when.isValid() ? - Ekos::Scheduler::findAltitude(job2->getTargetCoords(), when, &B_is_setting) : + findAltitude(job2->getTargetCoords(), when, &B_is_setting) : job2->altitudeAtStartup; // Sort with the setting target first @@ -704,3 +715,340 @@ { return job1->getStartupTime() < job2->getStartupTime(); } + +int16_t SchedulerJob::getAltitudeScore(QDateTime const &when) const +{ + // FIXME: block calculating target coordinates at a particular time is duplicated in several places + GeoLocation *geo = KStarsData::Instance()->geo(); + + // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! + KStarsDateTime ltWhen(when.isValid() ? + Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : + KStarsData::Instance()->lt()); + + // Create a sky object with the target catalog coordinates + SkyPoint const target = getTargetCoords(); + SkyObject o; + o.setRA0(target.ra0()); + o.setDec0(target.dec0()); + + // Update RA/DEC of the target for the current fraction of the day + KSNumbers numbers(ltWhen.djd()); + o.updateCoordsNow(&numbers); + + // Compute local sidereal time for the current fraction of the day, calculate altitude + CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); + o.EquatorialToHorizontal(&LST, geo->lat()); + double const altitude = o.alt().Degrees(); + + double const SETTING_ALTITUDE_CUTOFF = Options::settingAltitudeCutoff(); + int16_t score = BAD_SCORE - 1; + + // If altitude is negative, bad score + // FIXME: some locations may allow negative altitudes + if (altitude < 0) + { + score = BAD_SCORE; + } + else if (-90 < getMinAltitude()) + { + // If under altitude constraint, bad score + if (altitude < getMinAltitude()) + score = BAD_SCORE; + // Else if setting and under altitude cutoff, job would end soon after starting, bad score + // FIXME: half bad score when under altitude cutoff risk getting positive again + else + { + double offset = LST.Hours() - o.ra().Hours(); + if (24.0 <= offset) + offset -= 24.0; + else if (offset < 0.0) + offset += 24.0; + if (0.0 <= offset && offset < 12.0) + if (altitude - SETTING_ALTITUDE_CUTOFF < getMinAltitude()) + score = BAD_SCORE / 2; + } + } + // If not constrained but below minimum hard altitude, set score to 10% of altitude value + else if (altitude < MIN_ALTITUDE) + { + score = static_cast (altitude / 10.0); + } + + // Else default score calculation without altitude constraint + if (score < BAD_SCORE) + score = static_cast ((1.5 * pow(1.06, altitude)) - (MIN_ALTITUDE / 10.0)); + + //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' target altitude is %3 degrees at %2 (score %4).") + // .arg(getName()) + // .arg(when.toString(getDateTimeDisplayFormat())) + // .arg(currentAlt, 0, 'f', minAltitude->decimals()) + // .arg(QString::asprintf("%+d", score)); + + return score; +} + +int16_t SchedulerJob::getMoonSeparationScore(QDateTime const &when) const +{ + // FIXME: block calculating target coordinates at a particular time is duplicated in several places + GeoLocation *geo = KStarsData::Instance()->geo(); + + // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! + KStarsDateTime ltWhen(when.isValid() ? + Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : + KStarsData::Instance()->lt()); + + // Create a sky object with the target catalog coordinates + SkyPoint const target = getTargetCoords(); + SkyObject o; + o.setRA0(target.ra0()); + o.setDec0(target.dec0()); + + // Update RA/DEC of the target for the current fraction of the day + KSNumbers numbers(ltWhen.djd()); + o.updateCoordsNow(&numbers); + + // Update moon + //ut = geo->LTtoUT(ltWhen); + //KSNumbers ksnum(ut.djd()); // BUG: possibly LT.djd() != UT.djd() because of translation + //LST = geo->GSTtoLST(ut.gst()); + CachingDms LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); + moon->updateCoords(&numbers, true, geo->lat(), &LST, true); + + double const moonAltitude = moon->alt().Degrees(); + + // Lunar illumination % + double const illum = moon->illum() * 100.0; + + // Moon/Sky separation p + double const separation = moon->angularDistanceTo(&o).Degrees(); + + // Zenith distance of the moon + double const zMoon = (90 - moonAltitude); + // Zenith distance of target + double const zTarget = (90 - o.alt().Degrees()); + + int16_t score = 0; + + // If target = Moon, or no illuminiation, or moon below horizon, return static score. + if (zMoon == zTarget || illum == 0 || zMoon >= 90) + score = 100; + else + { + // JM: Some magic voodoo formula I came up with! + double moonEffect = (pow(separation, 1.7) * pow(zMoon, 0.5)) / (pow(zTarget, 1.1) * pow(illum, 0.5)); + + // Limit to 0 to 100 range. + moonEffect = KSUtils::clamp(moonEffect, 0.0, 100.0); + + if (getMinMoonSeparation() > 0) + { + if (separation < getMinMoonSeparation()) + score = BAD_SCORE * 5; + else + score = moonEffect; + } + else + score = moonEffect; + } + + // Limit to 0 to 20 + score /= 5.0; + + //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' target is %L3 degrees from Moon (score %2).") + // .arg(getName()) + // .arg(separation, 0, 'f', 3) + // .arg(QString::asprintf("%+d", score)); + + return score; +} + + +double SchedulerJob::getCurrentMoonSeparation() const +{ + // FIXME: block calculating target coordinates at a particular time is duplicated in several places + GeoLocation *geo = KStarsData::Instance()->geo(); + + // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! + KStarsDateTime ltWhen(KStarsData::Instance()->lt()); + + // Create a sky object with the target catalog coordinates + SkyPoint const target = getTargetCoords(); + SkyObject o; + o.setRA0(target.ra0()); + o.setDec0(target.dec0()); + + // Update RA/DEC of the target for the current fraction of the day + KSNumbers numbers(ltWhen.djd()); + o.updateCoordsNow(&numbers); + + // Update moon + //ut = geo->LTtoUT(ltWhen); + //KSNumbers ksnum(ut.djd()); // BUG: possibly LT.djd() != UT.djd() because of translation + //LST = geo->GSTtoLST(ut.gst()); + CachingDms LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); + moon->updateCoords(&numbers, true, geo->lat(), &LST, true); + + // Moon/Sky separation p + return moon->angularDistanceTo(&o).Degrees(); +} + +QDateTime SchedulerJob::calculateAltitudeTime(QDateTime const &when) const +{ + // FIXME: block calculating target coordinates at a particular time is duplicated in several places + GeoLocation *geo = KStarsData::Instance()->geo(); + + // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! + KStarsDateTime ltWhen(when.isValid() ? + Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : + KStarsData::Instance()->lt()); + + // Create a sky object with the target catalog coordinates + SkyPoint const target = getTargetCoords(); + SkyObject o; + o.setRA0(target.ra0()); + o.setDec0(target.dec0()); + + // Calculate the UT at the argument time + KStarsDateTime const ut = geo->LTtoUT(ltWhen); + + double const SETTING_ALTITUDE_CUTOFF = Options::settingAltitudeCutoff(); + + // Within the next 24 hours, search when the job target matches the altitude and moon constraints + for (unsigned int minute = 0; minute < 24 * 60; minute++) + { + KStarsDateTime const ltOffset(ltWhen.addSecs(minute * 60)); + + // Update RA/DEC of the target for the current fraction of the day + KSNumbers numbers(ltOffset.djd()); + o.updateCoordsNow(&numbers); + + // Compute local sidereal time for the current fraction of the day, calculate altitude + CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(ltOffset).gst()); + o.EquatorialToHorizontal(&LST, geo->lat()); + double const altitude = o.alt().Degrees(); + + if (getMinAltitude() <= altitude) + { + // Don't test proximity to dawn in this situation, we only cater for altitude here + + // Continue searching if Moon separation is not good enough + if (0 < getMinMoonSeparation() && getMoonSeparationScore(ltOffset) < 0) + continue; + + // Continue searching if target is setting and under the cutoff + double offset = LST.Hours() - o.ra().Hours(); + if (24.0 <= offset) + offset -= 24.0; + else if (offset < 0.0) + offset += 24.0; + if (0.0 <= offset && offset < 12.0) + if (altitude - SETTING_ALTITUDE_CUTOFF < getMinAltitude()) + continue; + + return ltOffset; + } + } + + return QDateTime(); +} + +QDateTime SchedulerJob::calculateCulmination(QDateTime const &when) const +{ + // FIXME: culmination calculation is a min altitude requirement, should be an interval altitude requirement + GeoLocation *geo = KStarsData::Instance()->geo(); + // FIXME: block calculating target coordinates at a particular time is duplicated in calculateCulmination + + // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! + KStarsDateTime ltWhen(when.isValid() ? + Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : + KStarsData::Instance()->lt()); + + // Create a sky object with the target catalog coordinates + SkyPoint const target = getTargetCoords(); + SkyObject o; + o.setRA0(target.ra0()); + o.setDec0(target.dec0()); + + // Update RA/DEC for the argument date/time + KSNumbers numbers(ltWhen.djd()); + o.updateCoordsNow(&numbers); + + // Calculate transit date/time at the argument date - transitTime requires UT and returns LocalTime + KStarsDateTime transitDateTime(ltWhen.date(), o.transitTime(geo->LTtoUT(ltWhen), geo), Qt::LocalTime); + + // Shift transit date/time by the argument offset + KStarsDateTime observationDateTime = transitDateTime.addSecs(getCulminationOffset() * 60); + + // Relax observation time, culmination calculation is stable at minute only + KStarsDateTime relaxedDateTime = observationDateTime.addSecs(Options::leadTime() * 60); + + // Verify resulting observation time is under lead time vs. argument time + // If sooner, delay by 8 hours to get to the next transit - perhaps in a third call + if (relaxedDateTime < ltWhen) + { + qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' startup %2 is posterior to transit %3, shifting by 8 hours.") + .arg(getName()) + .arg(ltWhen.toString(getDateTimeDisplayFormat())) + .arg(relaxedDateTime.toString(getDateTimeDisplayFormat())); + + return calculateCulmination(when.addSecs(8 * 60 * 60)); + } + + // Guarantees - culmination calculation is stable at minute level, so relax by lead time + Q_ASSERT_X(observationDateTime.isValid(), __FUNCTION__, "Observation time for target culmination is valid."); + Q_ASSERT_X(ltWhen <= relaxedDateTime, __FUNCTION__, "Observation time for target culmination is at or after than argument time"); + + // Return consolidated culmination time + return Qt::UTC == observationDateTime.timeSpec() ? geo->UTtoLT(observationDateTime) : observationDateTime; +} + +double SchedulerJob::findAltitude(const SkyPoint &target, const QDateTime &when, bool * is_setting, bool debug) +{ + // FIXME: block calculating target coordinates at a particular time is duplicated in several places + + GeoLocation * const geo = KStarsData::Instance()->geo(); + + // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! + KStarsDateTime ltWhen(when.isValid() ? + Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : + KStarsData::Instance()->lt()); + + // Create a sky object with the target catalog coordinates + SkyObject o; + o.setRA0(target.ra0()); + o.setDec0(target.dec0()); + + // Update RA/DEC of the target for the current fraction of the day + KSNumbers numbers(ltWhen.djd()); + o.updateCoordsNow(&numbers); + + // Calculate alt/az coordinates using KStars instance's geolocation + CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); + o.EquatorialToHorizontal(&LST, geo->lat()); + + // Hours are reduced to [0,24[, meridian being at 0 + double offset = LST.Hours() - o.ra().Hours(); + if (24.0 <= offset) + offset -= 24.0; + else if (offset < 0.0) + offset += 24.0; + bool const passed_meridian = 0.0 <= offset && offset < 12.0; + + if (debug) + qCDebug(KSTARS_EKOS_SCHEDULER) << QString("When:%9 LST:%8 RA:%1 RA0:%2 DEC:%3 DEC0:%4 alt:%5 setting:%6 HA:%7") + .arg(o.ra().toHMSString()) + .arg(o.ra0().toHMSString()) + .arg(o.dec().toHMSString()) + .arg(o.dec0().toHMSString()) + .arg(o.alt().Degrees()) + .arg(passed_meridian ? "yes" : "no") + .arg(o.ra().Hours()) + .arg(LST.toHMSString()) + .arg(ltWhen.toString("HH:mm:ss")); + + if (is_setting) + *is_setting = passed_meridian; + + return o.alt().Degrees(); +} diff --git a/kstars/fitsviewer/fitsdata.cpp b/kstars/fitsviewer/fitsdata.cpp --- a/kstars/fitsviewer/fitsdata.cpp +++ b/kstars/fitsviewer/fitsdata.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) #include diff --git a/kstars/fitsviewer/fitsviewer.cpp b/kstars/fitsviewer/fitsviewer.cpp --- a/kstars/fitsviewer/fitsviewer.cpp +++ b/kstars/fitsviewer/fitsviewer.cpp @@ -546,7 +546,7 @@ void FITSViewer::openFile() { QUrl fileURL = - QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open FITS Image"), lastURL, "FITS (*.fits *.fit)"); + QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open FITS Image"), lastURL, "FITS (*.fits *.fits.fz *.fit *.fts)"); if (fileURL.isEmpty()) return; diff --git a/kstars/indi/inditelescopelite.cpp b/kstars/indi/inditelescopelite.cpp --- a/kstars/indi/inditelescopelite.cpp +++ b/kstars/indi/inditelescopelite.cpp @@ -393,7 +393,6 @@ bool TelescopeLite::slew(SkyPoint *ScopeTarget) { - abort(); ISwitchVectorProperty *motionSP = baseDevice->getSwitch("ON_COORD_SET"); if (motionSP == nullptr) @@ -432,7 +431,6 @@ bool TelescopeLite::sync(SkyPoint *ScopeTarget) { - abort(); ISwitchVectorProperty *motionSP = baseDevice->getSwitch("ON_COORD_SET"); if (motionSP == nullptr) diff --git a/kstars/kstars.h b/kstars/kstars.h --- a/kstars/kstars.h +++ b/kstars/kstars.h @@ -742,10 +742,10 @@ void slotPolarisHourAngle(); /** Update comets orbital elements*/ - void slotUpdateComets(bool isAutoUpdate=false); + void slotUpdateComets(bool isAutoUpdate = false); /** Update asteroids orbital elements*/ - void slotUpdateAsteroids(bool isAutoUpdate=false); + void slotUpdateAsteroids(bool isAutoUpdate = false); /** Update list of recent supernovae*/ void slotUpdateSupernovae(); @@ -786,6 +786,12 @@ return m_SkyMap != nullptr; } + /** Was KStars started with the clock running, or paused? */ + bool isStartedWithClockRunning() + { + return StartClockRunning; + } + /// Set to true when the application is being closed static bool Closing; diff --git a/kstars/kstars.cpp b/kstars/kstars.cpp --- a/kstars/kstars.cpp +++ b/kstars/kstars.cpp @@ -212,7 +212,7 @@ // If we are starting paused, we need to change datetime in data if (StartClockRunning == false) { - qDebug() << "KStars is started in paused state."; + qCInfo(KSTARS) << "KStars is started in paused state."; if (datetimeSet == false) data()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); } diff --git a/kstars/kstars.kcfg b/kstars/kstars.kcfg --- a/kstars/kstars.kcfg +++ b/kstars/kstars.kcfg @@ -872,6 +872,10 @@ 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 + @@ -1674,6 +1678,9 @@ + + + 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. @@ -1697,10 +1704,10 @@ Set the maximum travel distance of an absolute focuser. 10000 - - - Specifies exposure value of CCD when performing focusing. Lower this value to avoid saturation of bright stars which adversely affects HFR measurement. Increase the value if no stars are detected. - 0.5 + + + Specifies gain value of CCD when performing focusing if supported by camera. + 0 diff --git a/kstars/kstarsinit.cpp b/kstars/kstarsinit.cpp --- a/kstars/kstarsinit.cpp +++ b/kstars/kstarsinit.cpp @@ -187,7 +187,15 @@ if (!StartClockRunning) ka->toggle(); QObject::connect(ka, SIGNAL(triggered()), this, SLOT(slotToggleTimer())); - //QObject::connect(data()->clock(), SIGNAL(clockToggled(bool)), ka, SLOT(setChecked(bool))); + + // If we are started in --paused state make sure the icon reflects that now + if (StartClockRunning == false) + { + QAction *a = actionCollection()->action("clock_startstop"); + if (a) + a->setIcon(QIcon::fromTheme("run-build-install-root")); + } + QObject::connect(data()->clock(), &SimClock::clockToggled, [ = ](bool toggled) { QAction *a = actionCollection()->action("clock_startstop"); diff --git a/kstars/oal/equipmentwriter.ui b/kstars/oal/equipmentwriter.ui --- a/kstars/oal/equipmentwriter.ui +++ b/kstars/oal/equipmentwriter.ui @@ -134,6 +134,11 @@ Cassegrain + + + Ritchey-Chretien + + @@ -227,7 +232,8 @@ Save - + + .. diff --git a/kstars/oal/scope.h b/kstars/oal/scope.h --- a/kstars/oal/scope.h +++ b/kstars/oal/scope.h @@ -47,6 +47,8 @@ double _focalLength, double _aperture); inline void setINDIDriver(const QString &driver) { m_INDIDriver = driver; } + QJsonObject toJson() const; + private: QString m_Id, m_Model, m_Vendor, m_Type, m_Name, m_INDIDriver; double m_FocalLength, m_Aperture; diff --git a/kstars/oal/scope.cpp b/kstars/oal/scope.cpp --- a/kstars/oal/scope.cpp +++ b/kstars/oal/scope.cpp @@ -17,6 +17,7 @@ ***************************************************************************/ #include "oal/scope.h" +#include void OAL::Scope::setScope(const QString &_id, const QString &_model, const QString &_vendor, const QString &_type, double _focalLength, double _aperture) { @@ -30,3 +31,17 @@ m_Name = _vendor + ' ' + _model + " (" + _id + ')'; } + +QJsonObject OAL::Scope::toJson() const +{ + return + { + {"id", m_Id}, + {"model", m_Model}, + {"vendor", m_Vendor}, + {"type", m_Type}, + {"name", m_Name}, + {"focal_length", m_FocalLength}, + {"aperture", m_Aperture}, + }; +} diff --git a/kstars/skymap.cpp b/kstars/skymap.cpp --- a/kstars/skymap.cpp +++ b/kstars/skymap.cpp @@ -222,12 +222,13 @@ void SkyMap::slotToggleInfoboxes(bool flag) { m_iboxes->setVisible(flag); + Options::setShowInfoBoxes(flag); } SkyMap::~SkyMap() { /* == Save infoxes status into Options == */ - Options::setShowInfoBoxes(m_iboxes->isVisibleTo(parentWidget())); + //Options::setShowInfoBoxes(m_iboxes->isVisibleTo(parentWidget())); // Time box Options::setPositionTimeBox(m_timeBox->pos()); Options::setShadeTimeBox(m_timeBox->shaded()); diff --git a/kstars/skyobjects/ksplanet.cpp b/kstars/skyobjects/ksplanet.cpp --- a/kstars/skyobjects/ksplanet.cpp +++ b/kstars/skyobjects/ksplanet.cpp @@ -24,6 +24,8 @@ #include #include +#include "kstars_debug.h" + KSPlanet::OrbitDataManager KSPlanet::odm; KSPlanet::OrbitDataManager::OrbitDataManager() @@ -204,7 +206,7 @@ epret.longitude = dms(0.0); epret.latitude = dms(0.0); epret.radius = 0.0; - qWarning() << "Could not get data for '" << name() << "'" << endl; + qCWarning(KSTARS) << "Could not get data for name:" << name() << "(" << untranslatedName() << ")" << endl; return; } @@ -217,9 +219,9 @@ sum[i] += odc.Lon[i][j].A * cos(odc.Lon[i][j].B + odc.Lon[i][j].C * Tau); /* qDebug() << "sum[" << i <<"] =" << sum[i] << - " A = " << odc.Lon[i][j].A << " B = " << odc.Lon[i][j].B << - " C = " << odc.Lon[i][j].C << endl; - */ + " A = " << odc.Lon[i][j].A << " B = " << odc.Lon[i][j].B << + " C = " << odc.Lon[i][j].C << endl; + */ } sum[i] *= Tpow[i]; //qDebug() << name() << " : sum[" << i << "] = " << sum[i]; @@ -256,7 +258,7 @@ /* qDebug() << name() << " pre: Lat = " << epret.latitude.toDMSString() << " Long = " << - epret.longitude.toDMSString() << " Dist = " << epret.radius << endl; + epret.longitude.toDMSString() << " Dist = " << epret.radius << endl; */ } diff --git a/kstars/time/kstarsdatetime.h b/kstars/time/kstarsdatetime.h --- a/kstars/time/kstarsdatetime.h +++ b/kstars/time/kstarsdatetime.h @@ -35,197 +35,232 @@ *a few days). *@note Local time and Local sideral time are not handled here. Because they depend on the *geographic location, they are part of the GeoLocation class. + *@note The default timespec is UTC unless the passed value has different timespec value. *@sa GeoLocation::GSTtoLST() *@sa GeoLocation::UTtoLT() *@author Jason Harris - *@version 1.0 + *@author Jasem Mutlaq + *@version 1.1 */ class KStarsDateTime : public QDateTime { - public: - /** - *@short Default constructor - *Creates a date/time at J2000 (noon on Jan 1, 200) - */ - KStarsDateTime(); - - /** - *@short Constructor - *Creates a date/time at the specified Julian Day. - *@p jd The Julian Day - */ - explicit KStarsDateTime(long double djd); - - /** - *@short Copy constructor - *@p kdt The KStarsDateTime object to copy. - */ - KStarsDateTime(const KStarsDateTime &kdt); - - /** - *@short Copy constructor - *@p qdt The QDateTime object to copy. - */ - explicit KStarsDateTime(const QDateTime &qdt); - - /** - *@short Constructor - *Create a KStarsDateTimne based on the specified Date and Time. - *@p _d The QDate to assign - *@p _t The QTime to assign - */ - KStarsDateTime(const QDate &_d, const QTime &_t, Qt::TimeSpec timeSpec = Qt::UTC); - - /** - *Assign the (long double) Julian Day value, which includes the time of day - *encoded in the fractional portion. - *@p jd the Julian Day value to assign. - */ - void setDJD(long double jd); - - /** - *Assign the Date according to a QDate object. - *@p d the QDate to assign - */ - void setDate(const QDate &d); - - /** - *Assign the Time according to a QTime object. - *@p t the QTime to assign - */ - void setTime(const QTime &t); - - /** - *@return a KStarsDateTime that is the given number of seconds later - *than this KStarsDateTime. - *@p s the number of seconds to add. The number can be negative. - */ - KStarsDateTime addSecs(double s) const; - - /** - *Modify the Date/Time by adding a number of days. - *@p nd the number of days to add. The number can be negative. - */ - inline KStarsDateTime addDays(int nd) const { return KStarsDateTime(djd() + (long double)nd); } - - inline bool operator==(const KStarsDateTime &d) const { return DJD == d.djd(); } - inline bool operator!=(const KStarsDateTime &d) const { return DJD != d.djd(); } - inline bool operator<(const KStarsDateTime &d) const { return DJD < d.djd(); } - inline bool operator<=(const KStarsDateTime &d) const { return DJD <= d.djd(); } - inline bool operator>(const KStarsDateTime &d) const { return DJD > d.djd(); } - inline bool operator>=(const KStarsDateTime &d) const { return DJD >= d.djd(); } - - /** - *@return the date and time according to the CPU clock - */ - static KStarsDateTime currentDateTime(); - - /** - *@return the UTC date and time according to the CPU clock - */ - static KStarsDateTime currentDateTimeUtc(); - - /** - *@return a KStarsDateTime object parsed from the given string. - *@note This function is format-agnostic; it will try several formats - *when parsing the string. - *@param s the string expressing the date/time to be parsed. - */ - static KStarsDateTime fromString(const QString &s); - - /** - *@return the julian day as a long double, including the time as the fractional portion. - */ - inline long double djd() const { return DJD; } - - /** - *@return The Greenwich Sidereal Time - *The Greenwich sidereal time is the Right Ascension coordinate that is currently transiting - *the Prime Meridian at the Royal Observatory in Greenwich, UK (longitude=0.0) - */ - dms gst() const; - - /** - *Convert a given Greenwich Sidereal Time to Universal Time (=Greenwich Mean Time). - *@p GST the Greenwich Sidereal Time to convert to Universal Time. - */ - QTime GSTtoUT(dms GST) const; // FIXME: Shouldn't this be static? - - /** - *@enum EpochType description options - *@note After 1976, the IAU standard for epochs is Julian Years. - */ - enum EpochType - { - JULIAN, /**< Julian epoch (see http://scienceworld.wolfram.com/astronomy/JulianEpoch.html) */ - BESSELIAN, /**< Besselian epoch (see http://scienceworld.wolfram.com/astronomy/BesselianEpoch.html) */ - }; - - /** - *@return the (Julian) epoch value of the Date/Time. - *@short This is (approximately) the year expressed as a floating-point value - *@sa setFromEpoch() - *@note The definition of Julian Epoch used here comes from http://scienceworld.wolfram.com/astronomy/JulianEpoch.html - */ - inline double epoch() const { return 2000.0 + (djd() - J2000) / 365.25; } - - /** - *Set the Date/Time from an epoch value, represented as a double. - *@p e the epoch value - *@sa epoch() - */ - bool setFromEpoch(double e, EpochType type); - - /** - *Set the Date/Time from an epoch value, represented as a string. - *@p e the epoch value - *@return true if date set successfully - *@sa epoch() - */ - bool setFromEpoch(const QString &e); - - /** - *Set the Date/Time from an epoch value, represented as a double. - *@p e the epoch value - *@note This method assumes that the epoch 1950.0 is Besselian, otherwise assumes that the epoch is a Julian epoch. This is provided for backward compatibility, and because custom catalogs may still use 1950.0 to mean B1950.0 despite the IAU standard for epochs being Julian. - *@sa epoch() - */ - void setFromEpoch(double e); - - /** - *@short Takes in an epoch and returns a Julian Date - *@return the Julian Date (date with fraction) - *@param epoch A floating-point year value specifying the Epoch - *@param type JULIAN or BESSELIAN depending on what convention the epoch is specified in - */ - static long double epochToJd(double epoch, EpochType type = JULIAN); - - /** - *@short Takes in a Julian Date and returns the corresponding epoch year in the given system - *@return the epoch as a floating-point year value - *@param jd Julian date - *@param type Epoch system (KStarsDateTime::JULIAN or KStarsDateTime::BESSELIAN) - */ - static double jdToEpoch(long double jd, EpochType type = JULIAN); - - /** - *@short Takes in a string and returns a Julian epoch - */ - static double stringToEpoch(const QString &eName, bool &ok); - - /** - * The following values were obtained from Eric Weisstein's world of science: - * http://scienceworld.wolfram.com/astronomy/BesselianEpoch.html - */ - constexpr static const double B1900 = 2415020.31352; // Julian date of B1900 epoch - constexpr static const double JD_PER_BYEAR = 365.242198781; // Julian days in a Besselian year - private: - /** - *@return the Greenwich Sidereal Time at 0h UT on this object's Date - *@note used internally by gst() and GSTtoUT() - */ - dms GSTat0hUT() const; - - long double DJD { 0 }; + public: + /** + *@short Default constructor + *Creates a date/time at J2000 (noon on Jan 1, 200) + *@note This sets the timespec to UTC. + */ + KStarsDateTime(); + + /** + *@short Constructor + *Creates a date/time at the specified Julian Day. + *@p jd The Julian Day + *@note This sets the timespec to UTC. + */ + explicit KStarsDateTime(long double djd); + + /** + *@short Copy constructor + *@p kdt The KStarsDateTime object to copy. + *@note The timespec is copied from kdt. + */ + KStarsDateTime(const KStarsDateTime &kdt); + + /** + *@short Copy constructor + *@p qdt The QDateTime object to copy. + *@note The timespec is copied from qdt. + */ + explicit KStarsDateTime(const QDateTime &qdt); + + /** + *@short Constructor + *Create a KStarsDateTimne based on the specified Date and Time. + *@p _d The QDate to assign + *@p _t The QTime to assign + *@p timespec The desired timespec, UTC by default. + */ + KStarsDateTime(const QDate &_d, const QTime &_t, Qt::TimeSpec timeSpec = Qt::UTC); + + /** + *Assign the static_cast Julian Day value, which includes the time of day + *encoded in the fractional portion. + *@p jd the Julian Day value to assign. + */ + void setDJD(long double jd); + + /** + *Assign the Date according to a QDate object. + *@p d the QDate to assign + */ + void setDate(const QDate &d); + + /** + *Assign the Time according to a QTime object. + *@p t the QTime to assign + *@note timespec is NOT changed even if the passed QTime has a different timespec than current. + */ + void setTime(const QTime &t); + + /** + *@return a KStarsDateTime that is the given number of seconds later + *than this KStarsDateTime. + *@p s the number of seconds to add. The number can be negative. + */ + KStarsDateTime addSecs(double s) const; + + /** + *Modify the Date/Time by adding a number of days. + *@p nd the number of days to add. The number can be negative. + */ + inline KStarsDateTime addDays(int nd) const + { + return KStarsDateTime(djd() + static_cast(nd)); + } + + inline bool operator==(const KStarsDateTime &d) const + { + return DJD == d.djd(); + } + inline bool operator!=(const KStarsDateTime &d) const + { + return DJD != d.djd(); + } + inline bool operator<(const KStarsDateTime &d) const + { + return DJD < d.djd(); + } + inline bool operator<=(const KStarsDateTime &d) const + { + return DJD <= d.djd(); + } + inline bool operator>(const KStarsDateTime &d) const + { + return DJD > d.djd(); + } + inline bool operator>=(const KStarsDateTime &d) const + { + return DJD >= d.djd(); + } + + /** + *@return the date and time according to the CPU clock + */ + static KStarsDateTime currentDateTime(); + + /** + *@return the UTC date and time according to the CPU clock + */ + static KStarsDateTime currentDateTimeUtc(); + + /** + *@return a KStarsDateTime object parsed from the given string. + *@note This function is format-agnostic; it will try several formats + *when parsing the string. + *@param s the string expressing the date/time to be parsed. + */ + static KStarsDateTime fromString(const QString &s); + + /** + *@return the julian day as a long double, including the time as the fractional portion. + */ + inline long double djd() const + { + return DJD; + } + + /** + *@return The Greenwich Sidereal Time + *The Greenwich sidereal time is the Right Ascension coordinate that is currently transiting + *the Prime Meridian at the Royal Observatory in Greenwich, UK (longitude=0.0) + */ + dms gst() const; + + /** + *Convert a given Greenwich Sidereal Time to Universal Time (=Greenwich Mean Time). + *@p GST the Greenwich Sidereal Time to convert to Universal Time. + */ + QTime GSTtoUT(dms GST) const; // FIXME: Shouldn't this be static? + + /** + *@enum EpochType description options + *@note After 1976, the IAU standard for epochs is Julian Years. + */ + enum EpochType + { + JULIAN, /**< Julian epoch (see http://scienceworld.wolfram.com/astronomy/JulianEpoch.html) */ + BESSELIAN, /**< Besselian epoch (see http://scienceworld.wolfram.com/astronomy/BesselianEpoch.html) */ + }; + + /** + *@return the (Julian) epoch value of the Date/Time. + *@short This is (approximately) the year expressed as a floating-point value + *@sa setFromEpoch() + *@note The definition of Julian Epoch used here comes from http://scienceworld.wolfram.com/astronomy/JulianEpoch.html + */ + inline double epoch() const + { + return 2000.0 + (djd() - J2000) / 365.25; + } + + /** + *Set the Date/Time from an epoch value, represented as a double. + *@p e the epoch value + *@sa epoch() + */ + bool setFromEpoch(double e, EpochType type); + + /** + *Set the Date/Time from an epoch value, represented as a string. + *@p e the epoch value + *@return true if date set successfully + *@sa epoch() + */ + bool setFromEpoch(const QString &e); + + /** + *Set the Date/Time from an epoch value, represented as a double. + *@p e the epoch value + *@note This method assumes that the epoch 1950.0 is Besselian, otherwise assumes that the epoch is a Julian epoch. This is provided for backward compatibility, and because custom catalogs may still use 1950.0 to mean B1950.0 despite the IAU standard for epochs being Julian. + *@sa epoch() + */ + void setFromEpoch(double e); + + /** + *@short Takes in an epoch and returns a Julian Date + *@return the Julian Date (date with fraction) + *@param epoch A floating-point year value specifying the Epoch + *@param type JULIAN or BESSELIAN depending on what convention the epoch is specified in + */ + static long double epochToJd(double epoch, EpochType type = JULIAN); + + /** + *@short Takes in a Julian Date and returns the corresponding epoch year in the given system + *@return the epoch as a floating-point year value + *@param jd Julian date + *@param type Epoch system (KStarsDateTime::JULIAN or KStarsDateTime::BESSELIAN) + */ + static double jdToEpoch(long double jd, EpochType type = JULIAN); + + /** + *@short Takes in a string and returns a Julian epoch + */ + static double stringToEpoch(const QString &eName, bool &ok); + + /** + * The following values were obtained from Eric Weisstein's world of science: + * http://scienceworld.wolfram.com/astronomy/BesselianEpoch.html + */ + constexpr static const double B1900 = 2415020.31352; // Julian date of B1900 epoch + constexpr static const double JD_PER_BYEAR = 365.242198781; // Julian days in a Besselian year + private: + /** + *@return the Greenwich Sidereal Time at 0h UT on this object's Date + *@note used internally by gst() and GSTtoUT() + */ + dms GSTat0hUT() const; + + long double DJD { 0 }; }; diff --git a/kstars/time/kstarsdatetime.cpp b/kstars/time/kstarsdatetime.cpp --- a/kstars/time/kstarsdatetime.cpp +++ b/kstars/time/kstarsdatetime.cpp @@ -26,6 +26,7 @@ KStarsDateTime::KStarsDateTime() : QDateTime() { + setTimeSpec(Qt::UTC); setDJD(J2000); } @@ -44,16 +45,16 @@ QTime _t = kdt.time(); QDate _d = kdt.date(); long double jdFrac = ( _t.hour()-12 + ( _t.minute() + ( _t.second() + _t.msec()/1000.)/60.)/60.)/24.; - DJD = (long double)( _d.toJulianDay() ) + jdFrac; + DJD = static_cast( _d.toJulianDay() ) + jdFrac; }*/ KStarsDateTime::KStarsDateTime(const QDateTime &qdt) : QDateTime(qdt) //, QDateTime::Spec::UTC() ) { // FIXME: This method might be buggy. Need to write some tests -- asimha (Oct 2016) QTime _t = qdt.time(); QDate _d = qdt.date(); long double jdFrac = (_t.hour() - 12 + (_t.minute() + (_t.second() + _t.msec() / 1000.) / 60.) / 60.) / 24.; - DJD = (long double)(_d.toJulianDay()) + jdFrac; + DJD = static_cast(_d.toJulianDay()) + jdFrac; setTimeSpec(qdt.timeSpec()); //setUtcOffset(qdt.utcOffset()); } @@ -64,13 +65,13 @@ { //don't call setDJD() because we don't need to compute the time; just set DJD directly long double jdFrac = (_t.hour() - 12 + (_t.minute() + (_t.second() + _t.msec() / 1000.) / 60.) / 60.) / 24.; - DJD = (long double)(_d.toJulianDay()) + jdFrac; + DJD = static_cast(_d.toJulianDay()) + jdFrac; } KStarsDateTime::KStarsDateTime(long double _jd) : QDateTime() { - setDJD(_jd); setTimeSpec(Qt::UTC); + setDJD(_jd); } //KStarsDateTime KStarsDateTime::currentDateTime( QDateTime::Spec spec ) { @@ -122,7 +123,7 @@ void KStarsDateTime::setDJD(long double _jd) { //QDateTime::setTimeSpec( QDateTime::Spec::UTC() ); - QDateTime::setTimeSpec(Qt::UTC); + //QDateTime::setTimeSpec(Qt::UTC); DJD = _jd; long int ijd = (long int)_jd; @@ -148,31 +149,31 @@ void KStarsDateTime::setDate(const QDate &_d) { //Save the JD fraction - long double jdFrac = djd() - (long double)(date().toJulianDay()); + long double jdFrac = djd() - static_cast(date().toJulianDay()); //set the integer portion of the JD and add back the JD fraction: - setDJD((long double)_d.toJulianDay() + jdFrac); + setDJD(static_cast(_d.toJulianDay()) + jdFrac); } KStarsDateTime KStarsDateTime::addSecs(double s) const { - long double ds = (long double)s / 86400.; + long double ds = static_cast(s) / 86400.; KStarsDateTime kdt(djd() + ds); kdt.setTimeSpec(timeSpec()); return kdt; } void KStarsDateTime::setTime(const QTime &_t) { - KStarsDateTime _dt(date(), _t); + KStarsDateTime _dt(date(), _t, timeSpec()); setDJD(_dt.djd()); } dms KStarsDateTime::gst() const { dms gst0 = GSTat0hUT(); - double hr = double(time().hour() - offsetFromUtc()/3600.0); + double hr = double(time().hour() - offsetFromUtc() / 3600.0); double mn = double(time().minute()); double sc = double(time().second()) + double(0.001 * time().msec()); double st = (hr + (mn + sc / 60.0) / 60.0) * SIDEREALSECOND; diff --git a/kstars/time/simclock.cpp b/kstars/time/simclock.cpp --- a/kstars/time/simclock.cpp +++ b/kstars/time/simclock.cpp @@ -58,7 +58,7 @@ lastelapsed = mselapsed; } - long double scaledsec = (long double)mselapsed * (long double)Scale / 1000.0; + long double scaledsec = static_cast(mselapsed) * static_cast(Scale) / 1000.0; UTC.setDJD(julianmark + scaledsec / (24. * 3600.)); // qDebug() << "tick() : JD = " << QLocale().toString( UTC.djd(), 7 ) << @@ -97,8 +97,8 @@ { //The single shot timer is needed because otherwise the animation is happening so frequently //that the kstars interface becomes too unresponsive. - //QTimer::singleShot(1, [this,backward] { setUTC(UTC.addSecs((long double)Scale * (backward ? -1 : 1))); }); - setUTC(UTC.addSecs((long double)Scale * (backward ? -1 : 1))); + //QTimer::singleShot(1, [this,backward] { setUTC(UTC.addSecs(static_castScale * (backward ? -1 : 1))); }); + setUTC(UTC.addSecs(static_cast(Scale) * (backward ? -1 : 1))); } else if (!ManualMode) tick(); diff --git a/kstars/tools/obslistwizard.cpp b/kstars/tools/obslistwizard.cpp --- a/kstars/tools/obslistwizard.cpp +++ b/kstars/tools/obslistwizard.cpp @@ -15,6 +15,7 @@ ***************************************************************************/ #include "obslistwizard.h" +#include "Options.h" #include "geolocation.h" #include "kstarsdata.h" @@ -66,22 +67,29 @@ connect(olw->updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateObjectCount())); // Enable the update count button when certain elements are changed - connect(olw->TypeList, SIGNAL(itemSelectionChanged()), this, SLOT(slotObjectCountDirty())); - connect(olw->ConstellationList, SIGNAL(itemSelectionChanged()), this, SLOT(slotObjectCountDirty())); - connect(olw->RAMin, SIGNAL(editingFinished()), this, SLOT(slotParseRegion())); - connect(olw->RAMax, SIGNAL(editingFinished()), this, SLOT(slotParseRegion())); - connect(olw->DecMin, SIGNAL(editingFinished()), this, SLOT(slotParseRegion())); - connect(olw->DecMax, SIGNAL(editingFinished()), this, SLOT(slotParseRegion())); - connect(olw->RA, SIGNAL(editingFinished()), this, SLOT(slotParseRegion())); - connect(olw->Dec, SIGNAL(editingFinished()), this, SLOT(slotParseRegion())); - connect(olw->Radius, SIGNAL(editingFinished()), this, SLOT(slotObjectCountDirty())); - connect(olw->Date, SIGNAL(dateChanged(QDate)), this, SLOT(slotObjectCountDirty())); - connect(olw->Mag, SIGNAL(valueChanged(double)), this, SLOT(slotObjectCountDirty())); - connect(olw->IncludeNoMag, SIGNAL(clicked()), this, SLOT(slotObjectCountDirty())); - connect(olw->timeTo, SIGNAL(timeChanged(QTime)), this, SLOT(slotObjectCountDirty())); - connect(olw->timeFrom, SIGNAL(timeChanged(QTime)), this, SLOT(slotObjectCountDirty())); - connect(olw->minAlt, SIGNAL(valueChanged(double)), this, SLOT(slotObjectCountDirty())); - connect(olw->maxAlt, SIGNAL(valueChanged(double)), this, SLOT(slotObjectCountDirty())); + connect(olw->TypeList, &QListWidget::itemSelectionChanged, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->ConstellationList, &QListWidget::itemSelectionChanged, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->RAMin, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion); + connect(olw->RAMax, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion); + connect(olw->DecMin, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion); + connect(olw->DecMax, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion); + connect(olw->RA, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion); + connect(olw->Dec, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion); + connect(olw->Radius, &QLineEdit::editingFinished, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->Date, &QDateEdit::dateChanged, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->Mag, static_cast(&QDoubleSpinBox::valueChanged), this, &ObsListWizard::slotObjectCountDirty); + connect(olw->IncludeNoMag, &QPushButton::clicked, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->timeTo, &QTimeEdit::timeChanged, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->timeFrom, &QTimeEdit::timeChanged, this, &ObsListWizard::slotObjectCountDirty); + connect(olw->minAlt, static_cast(&QDoubleSpinBox::valueChanged), this, &ObsListWizard::slotObjectCountDirty); + connect(olw->maxAlt, static_cast(&QDoubleSpinBox::valueChanged), this, &ObsListWizard::slotObjectCountDirty); + + olw->coverage->setValue(Options::obsListCoverage()); + connect(olw->coverage, static_cast(&QDoubleSpinBox::valueChanged), [&](double value) + { + Options::setObsListCoverage(value); + slotObjectCountDirty(); + }); connect(olw->SelectByDate, SIGNAL(clicked()), this, SLOT(slotToggleDateWidgets())); connect(olw->SelectByMagnitude, SIGNAL(clicked()), this, SLOT(slotToggleMagWidgets())); @@ -306,7 +314,7 @@ void ObsListWizard::slotParseRegion() { if (sender()->objectName() == "RAMin" || sender()->objectName() == "RAMax" || sender()->objectName() == "DecMin" || - sender()->objectName() == "DecMax") + sender()->objectName() == "DecMax") { if (!olw->RAMin->isEmpty() && !olw->RAMax->isEmpty() && !olw->DecMin->isEmpty() && !olw->DecMax->isEmpty()) { @@ -902,44 +910,74 @@ //Check altitude of object every hour from 18:00 to midnight //If it's ever above 15 degrees, flag it as visible - KStarsDateTime Evening(olw->Date->date(), QTime(18, 0, 0)); - KStarsDateTime Midnight(olw->Date->date().addDays(1), QTime(0, 0, 0)); + KStarsDateTime Evening(olw->Date->date(), QTime(18, 0, 0), Qt::LocalTime); + KStarsDateTime Midnight(olw->Date->date().addDays(1), QTime(0, 0, 0), Qt::LocalTime); double minAlt = 15, maxAlt = 90; // Or use user-selected values, if they're valid - if (olw->timeFrom->time() < olw->timeTo->time()) + if (olw->timeFrom->time().isValid() && olw->timeTo->time().isValid()) { Evening.setTime(olw->timeFrom->time()); Midnight.setTime(olw->timeTo->time()); - Midnight.setDate(olw->Date->date()); - } - if (olw->minAlt->value() < olw->maxAlt->value()) - { - minAlt = olw->minAlt->value(); - maxAlt = olw->maxAlt->value(); + // If time from < timeTo (e.g. 06:00 PM to 9:00 PM) + // then we stay on the same day. + if (olw->timeFrom->time() < olw->timeTo->time()) + { + Midnight.setDate(olw->Date->date()); + } + // Otherwise we advance by one day + else + { + Midnight.setDate(olw->Date->date().addDays(1)); + } } - bool visible = false; + minAlt = olw->minAlt->value(); + maxAlt = olw->maxAlt->value(); + + // This is the "relaxed" search mode + // where if the object obeys the restrictions in 50% of the time of the range + // then it qualifies as "visible" + double totalCount = 0, visibleCount = 0; for (KStarsDateTime t = Evening; t < Midnight; t = t.addSecs(3600.0)) { dms LST = geo->GSTtoLST(t.gst()); p.EquatorialToHorizontal(&LST, geo->lat()); - + totalCount++; if (p.alt().Degrees() >= minAlt && p.alt().Degrees() <= maxAlt) - { - visible = true; - break; - } + visibleCount++; } - if (visible) + // If the object is within the min/max alt at least coverage % of the time range + // then consider it visible + if (visibleCount / totalCount >= olw->coverage->value() / 100.0) return true; if (doAdjustCount) --ObjectCount; if (doBuildList) obsList().takeAt(obsList().indexOf(o)); return false; + + // This is the strict mode where ANY object that does not meet the min & max + // altitude at ANY time would be removed from the list. +#if 0 + for (KStarsDateTime t = Evening; t < Midnight; t = t.addSecs(3600.0)) + { + dms LST = geo->GSTtoLST(t.gst()); + p.EquatorialToHorizontal(&LST, geo->lat()); + if (p.alt().Degrees() < minAlt || p.alt().Degrees() > maxAlt) + { + if (doAdjustCount) + --ObjectCount; + if (doBuildList) + obsList().takeAt(obsList().indexOf(o)); + return false; + } + } + return true; +#endif + } diff --git a/kstars/tools/obslistwizard.ui b/kstars/tools/obslistwizard.ui --- a/kstars/tools/obslistwizard.ui +++ b/kstars/tools/obslistwizard.ui @@ -6,8 +6,8 @@ 0 0 - 578 - 502 + 489 + 372 @@ -706,78 +706,138 @@ - - - Qt::Horizontal - - - - 539 - 20 - - - - - - - - - - Select objects which are observable on: - - - - - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - From: - - - - - - - false - - - - - - - To: - - - - - - - false - - + + + + + false + + + 90.000000000000000 + + + 15.000000000000000 + + + + + + + From: + + + + + + + From: + + + + + + + Min. Altitude: + + + + + + + false + + + + + + + The object must obey the minimum and maximum altitudes at least this much percentage of the indicated time range. + + + Coverage: + + + + + + + The object must obey the minimum and maximum altitudes at least this much percentage of the indicated time range. + + + 100.000000000000000 + + + 40.000000000000000 + + + + + + + To: + + + + + + + Max. Altitude: + + + + + + + false + + + + + + + false + + + 90.000000000000000 + + + 90.000000000000000 + + + + + + + Select objects which are observable on: + + + + + + + false + + + + + + + false + + + + 0 + 0 + + + + Greenwich, United Kingdom + + + + @@ -795,103 +855,6 @@ - - - - - From: - - - - - - - false - - - - 0 - 0 - - - - Greenwich, United Kingdom - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Minimum Altitude: - - - - - - - false - - - 90.000000000000000 - - - 15.000000000000000 - - - - - - - Maximum Altitude: - - - - - - - false - - - 90.000000000000000 - - - 90.000000000000000 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - Qt::Vertical @@ -1112,7 +1075,6 @@ Dec Radius SelectByDate - LocationButton SelectByMagnitude IncludeNoMag diff --git a/org.kde.kstars.appdata.xml b/org.kde.kstars.appdata.xml --- a/org.kde.kstars.appdata.xml +++ b/org.kde.kstars.appdata.xml @@ -250,7 +250,7 @@
  • Herunterladbare Kataloge einschließlich Messier-Bilder, Abell-Katalog, Sharpless-Katalog und Lynds Dunkelnebel-Katalog
  • Downloadable catalogues including Messier Images, Abell Planetary Nebulae, Sharpless Catalogue, Lynds Dark Nebula Catalogue
  • Los catálogos que puede descargar contienen imágenes de objetos Messier, Nebulosas planetarias Abell, el Catálogo Sharpless y el catálogo de nebulosas oscuras Lynds
  • -
  • Catálogos dispoñíbeis para descarga entre os que atopará imaxes de Messier, as nebulosas planetarias Abell, o catálogo Sharpless ou o catálogo de nebulosa escura de Lynds.
  • +
  • Catálogos dispoñíbeis para descarga entre os que hai imaxes de Messier, as nebulosas planetarias Abell, o catálogo Sharpless ou o catálogo de nebulosa escura de Lynds.
  • Katalog yang dapat didownload termasuk Messier Images, Abell Planetary Nebulae, Sharpless Catalogue, Lynds Dark Nebula Catalog
  • Catalogi die kunnen worden gedownload, met inbegrip van Messier objecten, Abell planetaire nevels, Sharpless Catalogus, Lynds Dark Nebula Catalog (catalogus donkere nevels)
  • Katalogi do pobrania zawierające obrazy Messier, Mgławicę planetarną Abell, Katalog Sharpless, Katalog Lynds Dark Nebula