diff --git a/kstars/auxiliary/ksutils.cpp b/kstars/auxiliary/ksutils.cpp index 2dfff809d..b7213d52b 100644 --- a/kstars/auxiliary/ksutils.cpp +++ b/kstars/auxiliary/ksutils.cpp @@ -1,1655 +1,1655 @@ /*************************************************************************** ksutils.cpp - K Desktop Planetarium ------------------- begin : Mon Jan 7 10:48:09 EST 2002 copyright : (C) 2002 by Mark Hollomon email : mhh@mindspring.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "ksutils.h" #include "config-kstars.h" #include "ksnotification.h" #include "kstars_debug.h" #include "deepskyobject.h" #ifndef KSTARS_LITE #include "kswizard.h" #endif #include "Options.h" #include "starobject.h" #include "auxiliary/kspaths.h" #ifndef KSTARS_LITE #include #endif #ifdef HAVE_LIBRAW #include #endif #include #include #include namespace KSUtils { bool isHardwareLimited() { #ifdef __arm__ return true; #else return false; #endif } bool openDataFile(QFile &file, const QString &s) { QString FileName = KSPaths::locate(QStandardPaths::GenericDataLocation, s); if (!FileName.isNull()) { file.setFileName(FileName); return file.open(QIODevice::ReadOnly); } return false; } QString getDSSURL(const SkyPoint *const p) { double height, width; double dss_default_size = Options::defaultDSSImageSize(); double dss_padding = Options::dSSPadding(); Q_ASSERT(p); Q_ASSERT(dss_default_size > 0.0 && dss_padding >= 0.0); const DeepSkyObject *dso = dynamic_cast(p); // Decide what to do about the height and width if (dso) { // For deep-sky objects, use their height and width information double a, b, pa; a = dso->a(); b = dso->a() * dso->e(); // Use a * e instead of b, since e() returns 1 whenever one of the dimensions is zero. This is important for circular objects pa = dso->pa() * dms::DegToRad; // We now want to convert a, b, and pa into an image // height and width -- i.e. a dRA and dDec. // DSS uses dDec for height and dRA for width. (i.e. "top" is north in the DSS images, AFAICT) // From some trigonometry, assuming we have a rectangular object (worst case), we need: width = a * sin(pa) + b * cos(pa); height = a * cos(pa) + b * sin(pa); // 'a' and 'b' are in arcminutes, so height and width are in arcminutes // Pad the RA and Dec, so that we show more of the sky than just the object. height += dss_padding; width += dss_padding; } else { // For a generic sky object, we don't know what to do. So // we just assume the default size. height = width = dss_default_size; } // There's no point in tiny DSS images that are smaller than dss_default_size if (height < dss_default_size) height = dss_default_size; if (width < dss_default_size) width = dss_default_size; return getDSSURL(p->ra0(), p->dec0(), width, height); } QString getDSSURL(const dms &ra, const dms &dec, float width, float height, const QString &type) { const QString URLprefix("http://archive.stsci.edu/cgi-bin/dss_search?"); QString URLsuffix = QString("&e=J2000&f=%1&c=none&fov=NONE").arg(type); const double dss_default_size = Options::defaultDSSImageSize(); char decsgn = (dec.Degrees() < 0.0) ? '-' : '+'; int dd = abs(dec.degree()); int dm = abs(dec.arcmin()); int ds = abs(dec.arcsec()); // Infinite and NaN sizes are replaced by the default size and tiny DSS images are resized to default size if (!qIsFinite(height) || height <= 0.0) height = dss_default_size; if (!qIsFinite(width) || width <= 0.0) width = dss_default_size; // DSS accepts images that are no larger than 75 arcminutes if (height > 75.0) height = 75.0; if (width > 75.0) width = 75.0; QString RAString, DecString, SizeString; DecString = DecString.sprintf("&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds); RAString = RAString.sprintf("r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second()); SizeString = SizeString.sprintf("&h=%02.1f&w=%02.1f", height, width); return (URLprefix + RAString + DecString + SizeString + URLsuffix); } QString toDirectionString(dms angle) { // TODO: Instead of doing it this way, it would be nicer to // compute the string to arbitrary precision. Although that will // not be easy to localize. (Consider, for instance, Indian // languages that have special names for the intercardinal points) // -- asimha static const char *directions[] = { I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "N"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NNE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "ENE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "E"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "ESE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SSE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "S"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SSW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "WSW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "W"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "WNW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NNW"), I18N_NOOP2("Unknown cardinal / intercardinal direction", "???") }; int index = (int)((angle.reduce().Degrees() + 11.25) / 22.5); // A number between 0 and 16 (inclusive) is expected if (index < 0 || index > 16) index = 16; // Something went wrong. else index = (index == 16 ? 0 : index); return i18nc("Abbreviated cardinal / intercardinal etc. direction", directions[index]); } QList *castStarObjListToSkyObjList(QList *starObjList) { QList *skyObjList = new QList(); foreach (StarObject *so, *starObjList) { skyObjList->append(so); } return skyObjList; } QString constGenetiveFromAbbrev(const QString &code) { if (code == "And") return QString("Andromedae"); if (code == "Ant") return QString("Antliae"); if (code == "Aps") return QString("Apodis"); if (code == "Aqr") return QString("Aquarii"); if (code == "Aql") return QString("Aquilae"); if (code == "Ara") return QString("Arae"); if (code == "Ari") return QString("Arietis"); if (code == "Aur") return QString("Aurigae"); if (code == "Boo") return QString("Bootis"); if (code == "Cae") return QString("Caeli"); if (code == "Cam") return QString("Camelopardalis"); if (code == "Cnc") return QString("Cancri"); if (code == "CVn") return QString("Canum Venaticorum"); if (code == "CMa") return QString("Canis Majoris"); if (code == "CMi") return QString("Canis Minoris"); if (code == "Cap") return QString("Capricorni"); if (code == "Car") return QString("Carinae"); if (code == "Cas") return QString("Cassiopeiae"); if (code == "Cen") return QString("Centauri"); if (code == "Cep") return QString("Cephei"); if (code == "Cet") return QString("Ceti"); if (code == "Cha") return QString("Chamaeleontis"); if (code == "Cir") return QString("Circini"); if (code == "Col") return QString("Columbae"); if (code == "Com") return QString("Comae Berenices"); if (code == "CrA") return QString("Coronae Austrinae"); if (code == "CrB") return QString("Coronae Borealis"); if (code == "Crv") return QString("Corvi"); if (code == "Crt") return QString("Crateris"); if (code == "Cru") return QString("Crucis"); if (code == "Cyg") return QString("Cygni"); if (code == "Del") return QString("Delphini"); if (code == "Dor") return QString("Doradus"); if (code == "Dra") return QString("Draconis"); if (code == "Equ") return QString("Equulei"); if (code == "Eri") return QString("Eridani"); if (code == "For") return QString("Fornacis"); if (code == "Gem") return QString("Geminorum"); if (code == "Gru") return QString("Gruis"); if (code == "Her") return QString("Herculis"); if (code == "Hor") return QString("Horologii"); if (code == "Hya") return QString("Hydrae"); if (code == "Hyi") return QString("Hydri"); if (code == "Ind") return QString("Indi"); if (code == "Lac") return QString("Lacertae"); if (code == "Leo") return QString("Leonis"); if (code == "LMi") return QString("Leonis Minoris"); if (code == "Lep") return QString("Leporis"); if (code == "Lib") return QString("Librae"); if (code == "Lup") return QString("Lupi"); if (code == "Lyn") return QString("Lyncis"); if (code == "Lyr") return QString("Lyrae"); if (code == "Men") return QString("Mensae"); if (code == "Mic") return QString("Microscopii"); if (code == "Mon") return QString("Monocerotis"); if (code == "Mus") return QString("Muscae"); if (code == "Nor") return QString("Normae"); if (code == "Oct") return QString("Octantis"); if (code == "Oph") return QString("Ophiuchi"); if (code == "Ori") return QString("Orionis"); if (code == "Pav") return QString("Pavonis"); if (code == "Peg") return QString("Pegasi"); if (code == "Per") return QString("Persei"); if (code == "Phe") return QString("Phoenicis"); if (code == "Pic") return QString("Pictoris"); if (code == "Psc") return QString("Piscium"); if (code == "PsA") return QString("Piscis Austrini"); if (code == "Pup") return QString("Puppis"); if (code == "Pyx") return QString("Pyxidis"); if (code == "Ret") return QString("Reticuli"); if (code == "Sge") return QString("Sagittae"); if (code == "Sgr") return QString("Sagittarii"); if (code == "Sco") return QString("Scorpii"); if (code == "Scl") return QString("Sculptoris"); if (code == "Sct") return QString("Scuti"); if (code == "Ser") return QString("Serpentis"); if (code == "Sex") return QString("Sextantis"); if (code == "Tau") return QString("Tauri"); if (code == "Tel") return QString("Telescopii"); if (code == "Tri") return QString("Trianguli"); if (code == "TrA") return QString("Trianguli Australis"); if (code == "Tuc") return QString("Tucanae"); if (code == "UMa") return QString("Ursae Majoris"); if (code == "UMi") return QString("Ursae Minoris"); if (code == "Vel") return QString("Velorum"); if (code == "Vir") return QString("Virginis"); if (code == "Vol") return QString("Volantis"); if (code == "Vul") return QString("Vulpeculae"); return code; } QString constNameFromAbbrev(const QString &code) { if (code == "And") return QString("Andromeda"); if (code == "Ant") return QString("Antlia"); if (code == "Aps") return QString("Apus"); if (code == "Aqr") return QString("Aquarius"); if (code == "Aql") return QString("Aquila"); if (code == "Ara") return QString("Ara"); if (code == "Ari") return QString("Aries"); if (code == "Aur") return QString("Auriga"); if (code == "Boo") return QString("Bootes"); if (code == "Cae") return QString("Caelum"); if (code == "Cam") return QString("Camelopardalis"); if (code == "Cnc") return QString("Cancer"); if (code == "CVn") return QString("Canes Venatici"); if (code == "CMa") return QString("Canis Major"); if (code == "CMi") return QString("Canis Minor"); if (code == "Cap") return QString("Capricornus"); if (code == "Car") return QString("Carina"); if (code == "Cas") return QString("Cassiopeia"); if (code == "Cen") return QString("Centaurus"); if (code == "Cep") return QString("Cepheus"); if (code == "Cet") return QString("Cetus"); if (code == "Cha") return QString("Chamaeleon"); if (code == "Cir") return QString("Circinus"); if (code == "Col") return QString("Columba"); if (code == "Com") return QString("Coma Berenices"); if (code == "CrA") return QString("Corona Australis"); if (code == "CrB") return QString("Corona Borealis"); if (code == "Crv") return QString("Corvus"); if (code == "Crt") return QString("Crater"); if (code == "Cru") return QString("Crux"); if (code == "Cyg") return QString("Cygnus"); if (code == "Del") return QString("Delphinus"); if (code == "Dor") return QString("Doradus"); if (code == "Dra") return QString("Draco"); if (code == "Equ") return QString("Equuleus"); if (code == "Eri") return QString("Eridanus"); if (code == "For") return QString("Fornax"); if (code == "Gem") return QString("Gemini"); if (code == "Gru") return QString("Grus"); if (code == "Her") return QString("Hercules"); if (code == "Hor") return QString("Horologium"); if (code == "Hya") return QString("Hydra"); if (code == "Hyi") return QString("Hydrus"); if (code == "Ind") return QString("Indus"); if (code == "Lac") return QString("Lacerta"); if (code == "Leo") return QString("Leo"); if (code == "LMi") return QString("Leo Minor"); if (code == "Lep") return QString("Lepus"); if (code == "Lib") return QString("Libra"); if (code == "Lup") return QString("Lupus"); if (code == "Lyn") return QString("Lynx"); if (code == "Lyr") return QString("Lyra"); if (code == "Men") return QString("Mensa"); if (code == "Mic") return QString("Microscopium"); if (code == "Mon") return QString("Monoceros"); if (code == "Mus") return QString("Musca"); if (code == "Nor") return QString("Norma"); if (code == "Oct") return QString("Octans"); if (code == "Oph") return QString("Ophiuchus"); if (code == "Ori") return QString("Orion"); if (code == "Pav") return QString("Pavo"); if (code == "Peg") return QString("Pegasus"); if (code == "Per") return QString("Perseus"); if (code == "Phe") return QString("Phoenix"); if (code == "Pic") return QString("Pictor"); if (code == "Psc") return QString("Pisces"); if (code == "PsA") return QString("Piscis Austrinus"); if (code == "Pup") return QString("Puppis"); if (code == "Pyx") return QString("Pyxis"); if (code == "Ret") return QString("Reticulum"); if (code == "Sge") return QString("Sagitta"); if (code == "Sgr") return QString("Sagittarius"); if (code == "Sco") return QString("Scorpius"); if (code == "Scl") return QString("Sculptor"); if (code == "Sct") return QString("Scutum"); if (code == "Ser") return QString("Serpens"); if (code == "Sex") return QString("Sextans"); if (code == "Tau") return QString("Taurus"); if (code == "Tel") return QString("Telescopium"); if (code == "Tri") return QString("Triangulum"); if (code == "TrA") return QString("Triangulum Australe"); if (code == "Tuc") return QString("Tucana"); if (code == "UMa") return QString("Ursa Major"); if (code == "UMi") return QString("Ursa Minor"); if (code == "Vel") return QString("Vela"); if (code == "Vir") return QString("Virgo"); if (code == "Vol") return QString("Volans"); if (code == "Vul") return QString("Vulpecula"); return code; } QString constNameToAbbrev(const QString &fullName_) { QString fullName = fullName_.toLower(); if (fullName == "andromeda") return QString("And"); if (fullName == "antlia") return QString("Ant"); if (fullName == "apus") return QString("Aps"); if (fullName == "aquarius") return QString("Aqr"); if (fullName == "aquila") return QString("Aql"); if (fullName == "ara") return QString("Ara"); if (fullName == "aries") return QString("Ari"); if (fullName == "auriga") return QString("Aur"); if (fullName == "bootes") return QString("Boo"); if (fullName == "caelum") return QString("Cae"); if (fullName == "camelopardalis") return QString("Cam"); if (fullName == "cancer") return QString("Cnc"); if (fullName == "canes venatici") return QString("CVn"); if (fullName == "canis major") return QString("CMa"); if (fullName == "canis minor") return QString("CMi"); if (fullName == "capricornus") return QString("Cap"); if (fullName == "carina") return QString("Car"); if (fullName == "cassiopeia") return QString("Cas"); if (fullName == "centaurus") return QString("Cen"); if (fullName == "cepheus") return QString("Cep"); if (fullName == "cetus") return QString("Cet"); if (fullName == "chamaeleon") return QString("Cha"); if (fullName == "circinus") return QString("Cir"); if (fullName == "columba") return QString("Col"); if (fullName == "coma berenices") return QString("Com"); if (fullName == "corona australis") return QString("CrA"); if (fullName == "corona borealis") return QString("CrB"); if (fullName == "corvus") return QString("Crv"); if (fullName == "crater") return QString("Crt"); if (fullName == "crux") return QString("Cru"); if (fullName == "cygnus") return QString("Cyg"); if (fullName == "delphinus") return QString("Del"); if (fullName == "doradus") return QString("Dor"); if (fullName == "draco") return QString("Dra"); if (fullName == "equuleus") return QString("Equ"); if (fullName == "eridanus") return QString("Eri"); if (fullName == "fornax") return QString("For"); if (fullName == "gemini") return QString("Gem"); if (fullName == "grus") return QString("Gru"); if (fullName == "hercules") return QString("Her"); if (fullName == "horologium") return QString("Hor"); if (fullName == "hydra") return QString("Hya"); if (fullName == "hydrus") return QString("Hyi"); if (fullName == "indus") return QString("Ind"); if (fullName == "lacerta") return QString("Lac"); if (fullName == "leo") return QString("Leo"); if (fullName == "leo minor") return QString("LMi"); if (fullName == "lepus") return QString("Lep"); if (fullName == "libra") return QString("Lib"); if (fullName == "lupus") return QString("Lup"); if (fullName == "lynx") return QString("Lyn"); if (fullName == "lyra") return QString("Lyr"); if (fullName == "mensa") return QString("Men"); if (fullName == "microscopium") return QString("Mic"); if (fullName == "monoceros") return QString("Mon"); if (fullName == "musca") return QString("Mus"); if (fullName == "norma") return QString("Nor"); if (fullName == "octans") return QString("Oct"); if (fullName == "ophiuchus") return QString("Oph"); if (fullName == "orion") return QString("Ori"); if (fullName == "pavo") return QString("Pav"); if (fullName == "pegasus") return QString("Peg"); if (fullName == "perseus") return QString("Per"); if (fullName == "phoenix") return QString("Phe"); if (fullName == "pictor") return QString("Pic"); if (fullName == "pisces") return QString("Psc"); if (fullName == "piscis austrinus") return QString("PsA"); if (fullName == "puppis") return QString("Pup"); if (fullName == "pyxis") return QString("Pyx"); if (fullName == "reticulum") return QString("Ret"); if (fullName == "sagitta") return QString("Sge"); if (fullName == "sagittarius") return QString("Sgr"); if (fullName == "scorpius") return QString("Sco"); if (fullName == "sculptor") return QString("Scl"); if (fullName == "scutum") return QString("Sct"); if (fullName == "serpens") return QString("Ser"); if (fullName == "sextans") return QString("Sex"); if (fullName == "taurus") return QString("Tau"); if (fullName == "telescopium") return QString("Tel"); if (fullName == "triangulum") return QString("Tri"); if (fullName == "triangulum australe") return QString("TrA"); if (fullName == "tucana") return QString("Tuc"); if (fullName == "ursa major") return QString("UMa"); if (fullName == "ursa minor") return QString("UMi"); if (fullName == "vela") return QString("Vel"); if (fullName == "virgo") return QString("Vir"); if (fullName == "volans") return QString("Vol"); if (fullName == "vulpecula") return QString("Vul"); return fullName_; } QString constGenetiveToAbbrev(const QString &genetive_) { QString genetive = genetive_.toLower(); if (genetive == "andromedae") return QString("And"); if (genetive == "antliae") return QString("Ant"); if (genetive == "apodis") return QString("Aps"); if (genetive == "aquarii") return QString("Aqr"); if (genetive == "aquilae") return QString("Aql"); if (genetive == "arae") return QString("Ara"); if (genetive == "arietis") return QString("Ari"); if (genetive == "aurigae") return QString("Aur"); if (genetive == "bootis") return QString("Boo"); if (genetive == "caeli") return QString("Cae"); if (genetive == "camelopardalis") return QString("Cam"); if (genetive == "cancri") return QString("Cnc"); if (genetive == "canum venaticorum") return QString("CVn"); if (genetive == "canis majoris") return QString("CMa"); if (genetive == "canis minoris") return QString("CMi"); if (genetive == "capricorni") return QString("Cap"); if (genetive == "carinae") return QString("Car"); if (genetive == "cassiopeiae") return QString("Cas"); if (genetive == "centauri") return QString("Cen"); if (genetive == "cephei") return QString("Cep"); if (genetive == "ceti") return QString("Cet"); if (genetive == "chamaeleontis") return QString("Cha"); if (genetive == "circini") return QString("Cir"); if (genetive == "columbae") return QString("Col"); if (genetive == "comae berenices") return QString("Com"); if (genetive == "coronae austrinae") return QString("CrA"); if (genetive == "coronae borealis") return QString("CrB"); if (genetive == "corvi") return QString("Crv"); if (genetive == "crateris") return QString("Crt"); if (genetive == "crucis") return QString("Cru"); if (genetive == "cygni") return QString("Cyg"); if (genetive == "delphini") return QString("Del"); if (genetive == "doradus") return QString("Dor"); if (genetive == "draconis") return QString("Dra"); if (genetive == "equulei") return QString("Equ"); if (genetive == "eridani") return QString("Eri"); if (genetive == "fornacis") return QString("For"); if (genetive == "geminorum") return QString("Gem"); if (genetive == "gruis") return QString("Gru"); if (genetive == "herculis") return QString("Her"); if (genetive == "horologii") return QString("Hor"); if (genetive == "hydrae") return QString("Hya"); if (genetive == "hydri") return QString("Hyi"); if (genetive == "indi") return QString("Ind"); if (genetive == "lacertae") return QString("Lac"); if (genetive == "leonis") return QString("Leo"); if (genetive == "leonis minoris") return QString("LMi"); if (genetive == "leporis") return QString("Lep"); if (genetive == "librae") return QString("Lib"); if (genetive == "lupi") return QString("Lup"); if (genetive == "lyncis") return QString("Lyn"); if (genetive == "lyrae") return QString("Lyr"); if (genetive == "mensae") return QString("Men"); if (genetive == "microscopii") return QString("Mic"); if (genetive == "monocerotis") return QString("Mon"); if (genetive == "muscae") return QString("Mus"); if (genetive == "normae") return QString("Nor"); if (genetive == "octantis") return QString("Oct"); if (genetive == "ophiuchi") return QString("Oph"); if (genetive == "orionis") return QString("Ori"); if (genetive == "pavonis") return QString("Pav"); if (genetive == "pegasi") return QString("Peg"); if (genetive == "persei") return QString("Per"); if (genetive == "phoenicis") return QString("Phe"); if (genetive == "pictoris") return QString("Pic"); if (genetive == "piscium") return QString("Psc"); if (genetive == "piscis austrini") return QString("PsA"); if (genetive == "puppis") return QString("Pup"); if (genetive == "pyxidis") return QString("Pyx"); if (genetive == "reticuli") return QString("Ret"); if (genetive == "sagittae") return QString("Sge"); if (genetive == "sagittarii") return QString("Sgr"); if (genetive == "scorpii") return QString("Sco"); if (genetive == "sculptoris") return QString("Scl"); if (genetive == "scuti") return QString("Sct"); if (genetive == "serpentis") return QString("Ser"); if (genetive == "sextantis") return QString("Sex"); if (genetive == "tauri") return QString("Tau"); if (genetive == "telescopii") return QString("Tel"); if (genetive == "trianguli") return QString("Tri"); if (genetive == "trianguli australis") return QString("TrA"); if (genetive == "tucanae") return QString("Tuc"); if (genetive == "ursae majoris") return QString("UMa"); if (genetive == "ursae minoris") return QString("UMi"); if (genetive == "velorum") return QString("Vel"); if (genetive == "virginis") return QString("Vir"); if (genetive == "volantis") return QString("Vol"); if (genetive == "vulpeculae") return QString("Vul"); return genetive_; } QString Logging::_filename; void Logging::UseFile() { if (_filename.isEmpty()) { QDir dir; QString path = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "logs/" + QDateTime::currentDateTime().toString("yyyy-MM-dd"); dir.mkpath(path); QString name = "log_" + QDateTime::currentDateTime().toString("HH-mm-ss") + ".txt"; _filename = path + QStringLiteral("/") + name; // Clear file contents QFile file(_filename); file.open(QFile::WriteOnly); file.close(); } qSetMessagePattern("[%{time yyyy-MM-dd h:mm:ss.zzz t} %{if-debug}DEBG%{endif}%{if-info}INFO%{endif}%{if-warning}WARN%{endif}%{if-critical}CRIT%{endif}%{if-fatal}FATL%{endif}] %{if-category}[%{category}]%{endif} - %{message}"); qInstallMessageHandler(File); } void Logging::File(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QFile file(_filename); if (file.open(QFile::Append | QIODevice::Text)) { QTextStream stream(&file); Write(stream, type, context, msg); } } void Logging::UseStdout() { qSetMessagePattern("[%{time yyyy-MM-dd h:mm:ss.zzz t} %{if-debug}DEBG%{endif}%{if-info}INFO%{endif}%{if-warning}WARN%{endif}%{if-critical}CRIT%{endif}%{if-fatal}FATL%{endif}] %{if-category}[%{category}]%{endif} - %{message}"); qInstallMessageHandler(Stdout); } void Logging::Stdout(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QTextStream stream(stdout, QIODevice::WriteOnly); Write(stream, type, context, msg); } void Logging::UseStderr() { qInstallMessageHandler(Stderr); } void Logging::Stderr(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QTextStream stream(stderr, QIODevice::WriteOnly); Write(stream, type, context, msg); } void Logging::Write(QTextStream &stream, QtMsgType type, const QMessageLogContext &context, const QString &msg) { stream << QDateTime::currentDateTime().toString("[yyyy-MM-ddThh:mm:ss.zzz t "); switch (type) { case QtInfoMsg: stream << "INFO ]"; break; case QtDebugMsg: stream << "DEBG ]"; break; case QtWarningMsg: stream << "WARN ]"; break; case QtCriticalMsg: stream << "CRIT ]"; break; case QtFatalMsg: stream << "FATL ]"; break; default: stream << "UNKN ]"; } stream << "[" << qSetFieldWidth(30) << context.category << qSetFieldWidth(0) << "] - "; stream << msg << endl; //stream << qFormatLogMessage(type, context, msg) << endl; } void Logging::UseDefault() { qInstallMessageHandler(nullptr); } void Logging::Disable() { qInstallMessageHandler(Disabled); } void Logging::Disabled(QtMsgType, const QMessageLogContext &, const QString &) { } void Logging::SyncFilterRules() { // QString rules = QString("org.kde.kstars.ekos.debug=%1\n" // "org.kde.kstars.indi.debug=%2\n" // "org.kde.kstars.fits.debug=%3\n" // "org.kde.kstars.ekos.capture.debug=%4\n" // "org.kde.kstars.ekos.focus.debug=%5\n" // "org.kde.kstars.ekos.guide.debug=%6\n" // "org.kde.kstars.ekos.align.debug=%7\n" // "org.kde.kstars.ekos.mount.debug=%8\n" // "org.kde.kstars.ekos.scheduler.debug=%9\n").arg( // Options::verboseLogging() ? "true" : "false", // Options::iNDILogging() ? "true" : "false", // Options::fITSLogging() ? "true" : "false", // Options::captureLogging() ? "true" : "false", // Options::focusLogging() ? "true" : "false", // Options::guideLogging() ? "true" : "false", // Options::alignmentLogging() ? "true" : "false", // Options::mountLogging() ? "true" : "false", // Options::schedulerLogging() ? "true" : "false") // .append(QString("org.kde.kstars.ekos.observatory.debug=%2\n" // "org.kde.kstars.debug=%1").arg( // Options::verboseLogging() ? "true" : "false", // Options::observatoryLogging() ? "true" : "false")); QStringList rules; rules << "org.kde.kstars.ekos.debug" << (Options::verboseLogging() ? "true" : "false"); rules << "org.kde.kstars.indi.debug" << (Options::iNDILogging() ? "true" : "false"); rules << "org.kde.kstars.fits.debug" << (Options::fITSLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.capture.debug" << (Options::captureLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.focus.debug" << (Options::focusLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.guide.debug" << (Options::guideLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.align.debug" << (Options::alignmentLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.mount.debug" << (Options::mountLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.scheduler.debug" << (Options::schedulerLogging() ? "true" : "false"); rules << "org.kde.kstars.ekos.observatory.debug" << (Options::observatoryLogging() ? "true" : "false"); rules << "org.kde.kstars.debug" << (Options::verboseLogging() ? "true" : "false"); QString formattedRules; for (int i = 0; i < rules.size(); i += 2) formattedRules.append(QString("%1=%2\n").arg(rules[i], rules[i + 1])); QLoggingCategory::setFilterRules(formattedRules); } /** This method provides a centralized location for the default paths to important external files used in the Options on different operating systems. Note that on OS X, if the user builds the app without indi, astrometry, and xplanet internally then the options below will be used. If the user drags the app from a dmg and has to install the KStars data directory, then most of these paths will be overwritten since it is preferred to use the internal versions. **/ QString getDefaultPath(QString option) { QString snap = QProcessEnvironment::systemEnvironment().value("SNAP"); QString flat = QProcessEnvironment::systemEnvironment().value("FLATPAK_DEST"); if (option == "fitsDir") { return QDir::homePath(); } else if (option == "indiServer") { #if defined(Q_OS_OSX) return "/usr/local/bin/indiserver"; #endif if (flat.isEmpty() == false) return flat + "/bin/indiserver"; else return snap + "/usr/bin/indiserver"; } else if (option == "indiDriversDir") { #if defined(Q_OS_OSX) return "/usr/local/share/indi"; #elif defined(Q_OS_LINUX) if (flat.isEmpty() == false) return flat + "/share/indi"; else return snap + "/usr/share/indi"; #else return QStandardPaths::locate(QStandardPaths::GenericDataLocation, "indi", QStandardPaths::LocateDirectory); #endif } else if (option == "AstrometrySolverBinary") { #if defined(Q_OS_OSX) return "/usr/local/bin/solve-field"; #endif if (flat.isEmpty() == false) return flat + "/bin/solve-field"; else return snap + "/usr/bin/solve-field"; } else if (option == "AstrometryWCSInfo") { #if defined(Q_OS_OSX) return "/usr/local/bin/wcsinfo"; #endif if (flat.isEmpty() == false) return flat + "/bin/wcsinfo"; else return snap + "/usr/bin/wcsinfo"; } else if (option == "AstrometryConfFile") { #if defined(Q_OS_OSX) return "/usr/local/etc/astrometry.cfg"; #endif if (flat.isEmpty() == false) return flat + "/etc/astrometry.cfg"; else return snap + "/etc/astrometry.cfg"; } else if (option == "AstrometryIndexFileLocation") { #if defined(Q_OS_OSX) return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/Astrometry/"; #endif if (flat.isEmpty() == false) return flat + "/usr/share/astrometry/"; else return snap + "/usr/share/astrometry/"; } else if (option == "XplanetPath") { #if defined(Q_OS_OSX) return "/usr/local/bin/xplanet"; #endif if (flat.isEmpty() == false) return flat + "/bin/xplanet"; else return snap + "/usr/bin/xplanet"; } else if (option == "ASTAP") { #if defined(Q_OS_OSX) - return "/usr/local/bin/aspat"; + return "/Applications/ASTAP.app/Contents/MacOS/astap"; #elif defined(Q_OS_WIN) return "C:\Program Files\astap\astap.exe"; #endif if (flat.isEmpty() == false) return flat + "/bin/astap"; else return snap + "/usr/local/bin/astap"; } return QString(); } #if defined(Q_OS_OSX) //Note that this will copy and will not overwrite, so that the user's changes in the files are preserved. void copyResourcesFolderFromAppBundle(QString folder) { QString folderLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, folder, QStandardPaths::LocateDirectory); QDir folderSourceDir; if(folder == "kstars") folderSourceDir = QDir(QCoreApplication::applicationDirPath() + "/../Resources/data").absolutePath(); else folderSourceDir = QDir(QCoreApplication::applicationDirPath() + "/../Resources/" + folder).absolutePath(); if (folderSourceDir.exists()) { folderLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + '/' + folder; QDir writableDir; writableDir.mkdir(folderLocation); copyRecursively(folderSourceDir.absolutePath(), folderLocation); } } bool copyDataFolderFromAppBundleIfNeeded() //The method returns true if the data directory is good to go. { //This will copy the locale folder, the notifications folder, and the sounds folder and any missing files in them to Application Support if needed. copyResourcesFolderFromAppBundle("locale"); copyResourcesFolderFromAppBundle("knotifications5"); copyResourcesFolderFromAppBundle("sounds"); //This will check for the data directory and if its not present, it will run the wizard. QString dataLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory); if (dataLocation.isEmpty()) //If there is no kstars user data directory { QPointer wizard = new KSWizard(new QFrame()); wizard->exec(); //This will pause the startup until the user installs the data directory from the Wizard. dataLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory); if (dataLocation.isEmpty()) return false; //This sets some important OS X options. Options::setIndiServerIsInternal(true); Options::setIndiServer("*Internal INDI Server*"); Options::setIndiDriversAreInternal(true); Options::setIndiDriversDir("*Internal INDI Drivers*"); Options::setAstrometrySolverIsInternal(true); Options::setAstrometrySolverBinary("*Internal Solver*"); Options::setAstrometryConfFileIsInternal(true); Options::setAstrometryConfFile("*Internal astrometry.cfg*"); Options::setAstrometryWCSIsInternal(true); Options::setAstrometryWCSInfo("*Internal wcsinfo*"); Options::setAstrometryUseNoFITS2FITS(false); Options::setXplanetIsInternal(true); Options::setXplanetPath("*Internal XPlanet*"); Options::setRunStartupWizard(false); //don't run on startup because we are doing it now. return true; //This means the data directory is good to go now that we created it from the default. } //This will copy any of the critical KStars files from the app bundle to application support if they are missing. copyResourcesFolderFromAppBundle("kstars"); return true; //This means the data directory was good to go from the start and the wizard did not run. } //Can this and the linux method be merged somehow? See KSUtils::createLocalAstrometryConf bool configureAstrometry() { QStringList astrometryDataDirs = getAstrometryDataDirs(); if (astrometryDataDirs.count() == 0) return false; QString defaultAstrometryDataDir = getDefaultPath("AstrometryIndexFileLocation"); if(astrometryDataDirs.contains("IndexFileLocationNotYetSet")) replaceIndexFileNotYetSet(); if (QDir(defaultAstrometryDataDir).exists() == false) { if (KMessageBox::warningYesNo( nullptr, i18n("The selected Astrometry Index File Location:\n %1 \n does not exist. Do you want to make the directory?", defaultAstrometryDataDir), i18n("Make Astrometry Index File Directory?")) == KMessageBox::Yes) { if(QDir(defaultAstrometryDataDir).mkdir(defaultAstrometryDataDir)) { KSNotification::info(i18n("The Default Astrometry Index File Location was created.")); } else { KSNotification::sorry(i18n("The Default Astrometry Index File Directory does not exist and was not able to be created.")); } } else { return false; } } return true; } bool replaceIndexFileNotYetSet() { QString confPath = KSUtils::getAstrometryConfFilePath(); QFile confFile(confPath); QString contents; if (confFile.open(QIODevice::ReadOnly) == false) { KSNotification::error( i18n("Astrometry Configuration File Read Error.")); return false; } else { QByteArray fileContent = confFile.readAll(); confFile.close(); QString contents = QString::fromLatin1(fileContent); contents.replace("IndexFileLocationNotYetSet", getDefaultPath("AstrometryIndexFileLocation")); if (confFile.open(QIODevice::WriteOnly) == false) { KSNotification::error( i18n("Internal Astrometry Configuration File Write Error.")); return false; } else { QTextStream out(&confFile); out << contents; confFile.close(); } } return true; } bool copyRecursively(QString sourceFolder, QString destFolder) { QDir sourceDir(sourceFolder); if (!sourceDir.exists()) return false; QDir destDir(destFolder); if (!destDir.exists()) destDir.mkdir(destFolder); QStringList files = sourceDir.entryList(QDir::Files); for (int i = 0; i < files.count(); i++) { QString srcName = sourceFolder + QDir::separator() + files[i]; QString destName = destFolder + QDir::separator() + files[i]; QFile::copy(srcName, destName); //Note this does not overwrite files } files.clear(); files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (int i = 0; i < files.count(); i++) { QString srcName = sourceFolder + QDir::separator() + files[i]; QString destName = destFolder + QDir::separator() + files[i]; copyRecursively(srcName, destName); } return true; } #endif //Note maybe the Mac and Linux versions of creating the local astrometry conf file and index file folder can be merged. //I moved both of them here and this method references each one separately. //One is createLocalAstrometryConf and the other is configureAstrometry bool configureLocalAstrometryConfIfNecessary() { #if defined(Q_OS_LINUX) QString confPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("astrometry") + QLatin1String("/astrometry.cfg"); if (QFileInfo(confPath).exists() == false) { if(createLocalAstrometryConf() == false) return false; } #elif defined(Q_OS_OSX) if(configureAstrometry() == false) { KMessageBox::information( nullptr, i18n( "Failed to properly configure astrometry config file. Please click the options button in the lower right of the Astrometry Tab in Ekos to correct your settings. Then try starting Ekos again."), i18n("Astrometry Config File Error"), "astrometry_configuration_failure_warning"); return false; } #endif return true; } //Can this and the mac method be merged somehow? See KSUtils::configureAstrometry. bool createLocalAstrometryConf() { bool rc = false; QString confPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("astrometry") + QLatin1String("/astrometry.cfg"); QString systemConfPath = "/etc/astrometry.cfg"; // Check if directory already exists, if it doesn't create one QDir writableDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("astrometry")); if (writableDir.exists() == false) { rc = writableDir.mkdir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("astrometry")); if (rc == false) { qCCritical(KSTARS) << "Failed to create local astrometry directory"; return false; } } // Now copy system astrometry.cfg to local directory rc = QFile(systemConfPath).copy(confPath); if (rc == false) { qCCritical(KSTARS) << "Failed to copy" << systemConfPath << "to" << confPath; return false; } QFile localConf(confPath); // Open file and add our own path to it if (localConf.open(QFile::ReadWrite)) { QString all = localConf.readAll(); QStringList lines = all.split("\n"); for (int i = 0; i < lines.count(); i++) { if (lines[i].startsWith("add_path")) { lines.insert(i + 1, QString("add_path %1astrometry").arg(KSPaths::writableLocation(QStandardPaths::GenericDataLocation))); break; } } // Clear contents localConf.resize(0); // Now write back all the lines including our own inserted above QTextStream out(&localConf); for(const QString &line : lines) out << line << endl; localConf.close(); return true; } qCCritical(KSTARS) << "Failed to open local astrometry config" << confPath; return false; } QString getAstrometryConfFilePath() { #if defined(Q_OS_LINUX) if (Options::astrometryConfFileIsInternal()) return KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("astrometry") + QLatin1String("/astrometry.cfg"); #elif defined(Q_OS_OSX) if (Options::astrometryConfFileIsInternal()) return QCoreApplication::applicationDirPath() + "/astrometry/bin/astrometry.cfg"; #endif return Options::astrometryConfFile(); } QStringList getAstrometryDataDirs() { QStringList dataDirs; QString confPath = KSUtils::getAstrometryConfFilePath(); QFile confFile(confPath); if (confFile.open(QIODevice::ReadOnly) == false) { bool confFileExists = false; if(Options::astrometryConfFileIsInternal()) { if(KSUtils::configureLocalAstrometryConfIfNecessary()) { if (confFile.open(QIODevice::ReadOnly)) confFileExists = true; } } if(!confFileExists) { KSNotification::error(i18n("Astrometry configuration file corrupted or missing: %1\nPlease set the " "configuration file full path in INDI options.", confPath)); return dataDirs; } } QTextStream in(&confFile); QString line; while (!in.atEnd()) { line = in.readLine(); if (line.isEmpty() || line.startsWith('#')) continue; line = line.trimmed(); if (line.startsWith(QLatin1String("add_path"))) { dataDirs << line.mid(9).trimmed(); } } // if(dataDirs.size()==0) // KSNotification::error(i18n("Unable to find data dir in astrometry configuration file.")); return dataDirs; } bool addAstrometryDataDir(QString dataDir) { //This will need to be fixed! //if(Options::astrometryIndexFileLocation() != dataDir) // Options::setAstrometryIndexFileLocation(dataDir); QString confPath = KSUtils::getAstrometryConfFilePath(); QStringList astrometryDataDirs = getAstrometryDataDirs(); QFile confFile(confPath); QString contents; if (confFile.open(QIODevice::ReadOnly) == false) { KSNotification::error( i18n("Astrometry Configuration File Read Error.")); return false; } else { QTextStream in(&confFile); QString line; bool foundSpot = false; while (!in.atEnd()) { line = in.readLine(); if (line.trimmed().startsWith(QLatin1String("add_path"))) { if (!foundSpot) { foundSpot = true; for(QString astrometryDataDir : astrometryDataDirs) contents += "add_path " + astrometryDataDir + '\n'; contents += "add_path " + dataDir + '\n'; } else { //Do not keep adding the other add_paths because they just got added in the seciton above. } } else { contents += line + '\n'; } } if(!foundSpot) { for(QString astrometryDataDir : astrometryDataDirs) contents += "add_path " + astrometryDataDir + '\n'; contents += "add_path " + dataDir + '\n'; } confFile.close(); if (confFile.open(QIODevice::WriteOnly) == false) { KSNotification::error( i18n("Internal Astrometry Configuration File Write Error.")); return false; } else { QTextStream out(&confFile); out << contents; confFile.close(); } } return true; } bool removeAstrometryDataDir(QString dataDir) { QString confPath = KSUtils::getAstrometryConfFilePath(); QStringList astrometryDataDirs = getAstrometryDataDirs(); QFile confFile(confPath); QString contents; if (confFile.open(QIODevice::ReadOnly) == false) { KSNotification::error( i18n("Astrometry Configuration File Read Error.")); return false; } else { QTextStream in(&confFile); QString line; while (!in.atEnd()) { line = in.readLine(); if (line.mid(9).trimmed() != dataDir) { contents += line + '\n'; } } confFile.close(); if (confFile.open(QIODevice::WriteOnly) == false) { KSNotification::error( i18n("Internal Astrometry Configuration File Write Error.")); return false; } else { QTextStream out(&confFile); out << contents; confFile.close(); } } return true; } QByteArray getJPLQueryString(const QByteArray &kind, const QByteArray &dataFields, const QVector &filters) { QByteArray query("obj_group=all&obj_kind=" + kind + "&obj_numbered=all&OBJ_field=0&ORB_field=0"); // Apply filters: for (int i = 0; i < filters.length(); i++) { QByteArray f = QByteArray::number(i + 1); query += "&c" + f + "_group=OBJ&c1_item=" + filters[i].item + "&c" + f + "_op=" + filters[i].op + "&c" + f + "_value=" + filters[i].value; } // Apply query data fields... query += "&c_fields=" + dataFields; query += "&table_format=CSV&max_rows=10&format_option=full&query=Generate%20Table&." "cgifields=format_option&.cgifields=field_list&.cgifields=obj_kind&.cgifie" "lds=obj_group&.cgifields=obj_numbered&.cgifields=combine_mode&.cgifields=" "ast_orbit_class&.cgifields=table_format&.cgifields=ORB_field_set&.cgifiel" "ds=OBJ_field_set&.cgifields=preset_field_set&.cgifields=com_orbit_class"; return query; } bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMessage) { #ifndef HAVE_LIBRAW errorMessage = i18n("Unable to find dcraw and cjpeg. Please install the required tools to convert CR2/NEF to JPEG."); return false; #else int ret = 0; // Creation of image processing object LibRaw RawProcessor; // Let us open the file if ((ret = RawProcessor.open_file(rawImage.toLatin1().data())) != LIBRAW_SUCCESS) { errorMessage = i18n("Cannot open %1: %2", rawImage, libraw_strerror(ret)); RawProcessor.recycle(); return false; } // Let us unpack the thumbnail if ((ret = RawProcessor.unpack_thumb()) != LIBRAW_SUCCESS) { errorMessage = i18n("Cannot unpack_thumb %1: %2", rawImage, libraw_strerror(ret)); RawProcessor.recycle(); return false; } else // We have successfully unpacked the thumbnail, now let us write it to a file { //snprintf(thumbfn,sizeof(thumbfn),"%s.%s",av[i],T.tformat == LIBRAW_THUMBNAIL_JPEG ? "thumb.jpg" : "thumb.ppm"); if (LIBRAW_SUCCESS != (ret = RawProcessor.dcraw_thumb_writer(output.toLatin1().data()))) { errorMessage = i18n("Cannot write %s %1: %2", output, libraw_strerror(ret)); RawProcessor.recycle(); return false; } } return true; #endif } } diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp index 174c72bc1..30ca1195a 100644 --- a/kstars/ekos/guide/guide.cpp +++ b/kstars/ekos/guide/guide.cpp @@ -1,3686 +1,3699 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "guide.h" #include "guideadaptor.h" #include "kstars.h" #include "ksmessagebox.h" #include "ksnotification.h" #include "kstarsdata.h" #include "opscalibration.h" #include "opsguide.h" #include "Options.h" #include "auxiliary/QProgressIndicator.h" #include "ekos/auxiliary/darklibrary.h" #include "externalguide/linguider.h" #include "externalguide/phd2.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsviewer.h" #include "internalguide/internalguider.h" #include #include #include #include "ui_manualdither.h" #define CAPTURE_TIMEOUT_THRESHOLD 30000 namespace Ekos { Guide::Guide() : QWidget() { // #1 Setup UI setupUi(this); // #2 Register DBus qRegisterMetaType("Ekos::GuideState"); qDBusRegisterMetaType(); new GuideAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Guide", this); // #3 Init Plots initPlots(); // #4 Init View initView(); // #5 Load all settings loadSettings(); // #6 Init Connections initConnections(); // Image Filters for (auto &filter : FITSViewer::filterTypes) filterCombo->addItem(filter); // Progress Indicator pi = new QProgressIndicator(this); controlLayout->addWidget(pi, 1, 2, 1, 1); showFITSViewerB->setIcon( QIcon::fromTheme("kstars_fitsviewer")); connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Guide::showFITSViewer); showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideAutoScaleGraphB->setIcon( QIcon::fromTheme("zoom-fit-best")); connect(guideAutoScaleGraphB, &QPushButton::clicked, this, &Ekos::Guide::slotAutoScaleGraphs); guideAutoScaleGraphB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideSaveDataB->setIcon( QIcon::fromTheme("document-save")); connect(guideSaveDataB, &QPushButton::clicked, this, &Ekos::Guide::exportGuideData); guideSaveDataB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideDataClearB->setIcon( QIcon::fromTheme("application-exit")); connect(guideDataClearB, &QPushButton::clicked, this, &Ekos::Guide::clearGuideGraphs); guideDataClearB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Exposure //Should we set the range for the spin box here? QList exposureValues; exposureValues << 0.02 << 0.05 << 0.1 << 0.2 << 0.5 << 1 << 1.5 << 2 << 2.5 << 3 << 3.5 << 4 << 4.5 << 5 << 6 << 7 << 8 << 9 << 10 << 15 << 30; exposureIN->setRecommendedValues(exposureValues); connect(exposureIN, &NonLinearDoubleSpinBox::editingFinished, this, &Ekos::Guide::saveDefaultGuideExposure); // Init Internal Guider always internalGuider = new InternalGuider(); KConfigDialog *dialog = new KConfigDialog(this, "guidesettings", Options::self()); opsCalibration = new OpsCalibration(internalGuider); KPageWidgetItem *page = dialog->addPage(opsCalibration, i18n("Calibration")); page->setIcon(QIcon::fromTheme("tool-measure")); opsGuide = new OpsGuide(); connect(opsGuide, &OpsGuide::settingsUpdated, [this]() { onThresholdChanged(Options::guideAlgorithm()); configurePHD2Camera(); }); page = dialog->addPage(opsGuide, i18n("Guide")); page->setIcon(QIcon::fromTheme("kstars_guides")); internalGuider->setGuideView(guideView); // Set current guide type setGuiderType(-1); //This allows the current guideSubframe option to be loaded. if(guiderType == GUIDE_PHD2) setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); } Guide::~Guide() { delete guider; } void Guide::handleHorizontalPlotSizeChange() { driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); driftPlot->replot(); } void Guide::handleVerticalPlotSizeChange() { driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); driftPlot->replot(); } +void Guide::guideAfterMeridianFlip() +{ + //This will clear the tracking box selection + //The selected guide star is no longer valid due to the flip + guideView->setTrackingBoxEnabled(false); + starCenter = QVector3D(); + + if (Options::resetGuideCalibration()) + clearCalibration(); + guide(); + +} + void Guide::resizeEvent(QResizeEvent *event) { if (event->oldSize().width() != -1) { if (event->oldSize().width() != size().width()) handleHorizontalPlotSizeChange(); else if (event->oldSize().height() != size().height()) handleVerticalPlotSizeChange(); } else { QTimer::singleShot(10, this, &Ekos::Guide::handleHorizontalPlotSizeChange); } } void Guide::buildTarget() { double accuracyRadius = accuracyRadiusSpin->value(); Options::setGuiderAccuracyThreshold(accuracyRadius); if (centralTarget) { concentricRings->data()->clear(); redTarget->data()->clear(); yellowTarget->data()->clear(); centralTarget->data()->clear(); } else { concentricRings = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); redTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); yellowTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); centralTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); } const int pointCount = 200; QVector circleRings( pointCount * (5)); //Have to multiply by the number of rings, Rings at : 25%, 50%, 75%, 125%, 175% QVector circleCentral(pointCount); QVector circleYellow(pointCount); QVector circleRed(pointCount); int circleRingPt = 0; for (int i = 0; i < pointCount; i++) { double theta = i / static_cast(pointCount) * 2 * M_PI; for (double ring = 1; ring < 8; ring++) { if (ring != 4 && ring != 6) { if (i % (9 - static_cast(ring)) == 0) //This causes fewer points to draw on the inner circles. { circleRings[circleRingPt] = QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta), accuracyRadius * ring * 0.25 * qSin(theta)); circleRingPt++; } } } circleCentral[i] = QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta)); circleYellow[i] = QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta)); circleRed[i] = QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta)); } concentricRings->setLineStyle(QCPCurve::lsNone); concentricRings->setScatterSkip(0); concentricRings->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QColor(255, 255, 255, 150), 1)); concentricRings->data()->set(circleRings, true); redTarget->data()->set(circleRed, true); yellowTarget->data()->set(circleYellow, true); centralTarget->data()->set(circleCentral, true); concentricRings->setPen(QPen(Qt::white)); redTarget->setPen(QPen(Qt::red)); yellowTarget->setPen(QPen(Qt::yellow)); centralTarget->setPen(QPen(Qt::green)); concentricRings->setBrush(Qt::NoBrush); redTarget->setBrush(QBrush(QColor(255, 0, 0, 50))); yellowTarget->setBrush( QBrush(QColor(0, 255, 0, 50))); //Note this is actually yellow. It is green on top of red with equal opacity. centralTarget->setBrush(QBrush(QColor(0, 255, 0, 50))); if (driftPlot->size().width() > 0) driftPlot->replot(); } void Guide::clearGuideGraphs() { driftGraph->graph(0)->data()->clear(); //RA data driftGraph->graph(1)->data()->clear(); //DEC data driftGraph->graph(2)->data()->clear(); //RA highlighted point driftGraph->graph(3)->data()->clear(); //DEC highlighted point driftGraph->graph(4)->data()->clear(); //RA Pulses driftGraph->graph(5)->data()->clear(); //DEC Pulses driftPlot->graph(0)->data()->clear(); //Guide data driftPlot->graph(1)->data()->clear(); //Guide highlighted point driftGraph->clearItems(); //Clears dither text items from the graph driftGraph->replot(); driftPlot->replot(); //Since the labels got cleared with clearItems above. setupNSEWLabels(); } void Guide::setupNSEWLabels() { //Labels for N/S/E/W QColor raLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); QColor deLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); //DriftGraph { QCPItemText *northLabel = new QCPItemText(driftGraph); northLabel->setColor(deLabelColor); northLabel->setText(i18nc("North","N")); northLabel->position->setType(QCPItemPosition::ptViewportRatio); northLabel->position->setCoords(0.6,0.1); northLabel->setVisible(true); QCPItemText *southLabel = new QCPItemText(driftGraph); southLabel->setColor(deLabelColor); southLabel->setText(i18nc("South","S")); southLabel->position->setType(QCPItemPosition::ptViewportRatio); southLabel->position->setCoords(0.6,0.8); southLabel->setVisible(true); QCPItemText *westLabel = new QCPItemText(driftGraph); westLabel->setColor(raLabelColor); westLabel->setText(i18nc("West","W")); westLabel->position->setType(QCPItemPosition::ptViewportRatio); westLabel->position->setCoords(0.8,0.1); westLabel->setVisible(true); QCPItemText *eastLabel = new QCPItemText(driftGraph); eastLabel->setColor(raLabelColor); eastLabel->setText(i18nc("East","E")); eastLabel->position->setType(QCPItemPosition::ptViewportRatio); eastLabel->position->setCoords(0.8,0.8); eastLabel->setVisible(true); } //DriftPlot { QCPItemText *northLabel = new QCPItemText(driftPlot); northLabel->setColor(deLabelColor); northLabel->setText(i18nc("North","N")); northLabel->position->setType(QCPItemPosition::ptViewportRatio); northLabel->position->setCoords(0.25,0.2); northLabel->setVisible(true); QCPItemText *southLabel = new QCPItemText(driftPlot); southLabel->setColor(deLabelColor); southLabel->setText(i18nc("South","S")); southLabel->position->setType(QCPItemPosition::ptViewportRatio); southLabel->position->setCoords(0.25,0.7); southLabel->setVisible(true); QCPItemText *westLabel = new QCPItemText(driftPlot); westLabel->setColor(raLabelColor); westLabel->setText(i18nc("West","W")); westLabel->position->setType(QCPItemPosition::ptViewportRatio); westLabel->position->setCoords(0.8,0.75); westLabel->setVisible(true); QCPItemText *eastLabel = new QCPItemText(driftPlot); eastLabel->setColor(raLabelColor); eastLabel->setText(i18nc("East","E")); eastLabel->position->setType(QCPItemPosition::ptViewportRatio); eastLabel->position->setCoords(0.3,0.75); eastLabel->setVisible(true); } } void Guide::slotAutoScaleGraphs() { double accuracyRadius = accuracyRadiusSpin->value(); double key = guideTimer.elapsed() / 1000.0; driftGraph->xAxis->setRange(key - 60, key); driftGraph->yAxis->setRange(-3, 3); driftGraph->graph(0)->rescaleValueAxis(true); driftGraph->replot(); driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->graph(0)->rescaleAxes(true); driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); driftPlot->replot(); } void Guide::guideHistory() { int sliderValue = guideSlider->value(); latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); driftGraph->graph(2)->data()->clear(); //Clear RA highlighted point driftGraph->graph(3)->data()->clear(); //Clear DEC highlighted point driftPlot->graph(1)->data()->clear(); //Clear Guide highlighted point double t = driftGraph->graph(0)->dataMainKey(sliderValue); //Get time from RA data double ra = driftGraph->graph(0)->dataMainValue(sliderValue); //Get RA from RA data double de = driftGraph->graph(1)->dataMainValue(sliderValue); //Get DEC from DEC data double raPulse = driftGraph->graph(4)->dataMainValue(sliderValue); //Get RA Pulse from RA pulse data double dePulse = driftGraph->graph(5)->dataMainValue(sliderValue); //Get DEC Pulse from DEC pulse data driftGraph->graph(2)->addData(t, ra); //Set RA highlighted point driftGraph->graph(3)->addData(t, de); //Set DEC highlighted point //This will allow the graph to scroll left and right along with the guide slider if (driftGraph->xAxis->range().contains(t) == false) { if(t < driftGraph->xAxis->range().lower) { driftGraph->xAxis->setRange(t, t + driftGraph->xAxis->range().size()); } if(t > driftGraph->xAxis->range().upper) { driftGraph->xAxis->setRange(t - driftGraph->xAxis->range().size(), t); } } driftGraph->replot(); driftPlot->graph(1)->addData(ra, de); //Set guide highlighted point driftPlot->replot(); if(!graphOnLatestPt) { QTime localTime = guideTimer; localTime = localTime.addSecs(t); QPoint localTooltipCoordinates = driftGraph->graph(0)->dataPixelPosition(sliderValue).toPoint(); QPoint globalTooltipCoordinates = driftGraph->mapToGlobal(localTooltipCoordinates); if(raPulse == 0 && dePulse == 0) { QToolTip::showText( globalTooltipCoordinates, i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds", "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
", localTime.toString("hh:mm:ss AP"), QString::number(ra, 'f', 2), QString::number(de, 'f', 2))); } else { QToolTip::showText( globalTooltipCoordinates, i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is RA Pulse in ms; %5 is DE Pulse in ms", "" "" "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
RA Pulse: %4 ms
DE Pulse: %5 ms
", localTime.toString("hh:mm:ss AP"), QString::number(ra, 'f', 2), QString::number(de, 'f', 2), QString::number(raPulse, 'f', 2), QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph. } } } void Guide::setLatestGuidePoint(bool isChecked) { graphOnLatestPt = isChecked; if(isChecked) guideSlider->setValue(guideSlider->maximum()); } void Guide::toggleShowRAPlot(bool isChecked) { Options::setRADisplayedOnGuideGraph(isChecked); driftGraph->graph(0)->setVisible(isChecked); driftGraph->graph(2)->setVisible(isChecked); driftGraph->replot(); } void Guide::toggleShowDEPlot(bool isChecked) { Options::setDEDisplayedOnGuideGraph(isChecked); driftGraph->graph(1)->setVisible(isChecked); driftGraph->graph(3)->setVisible(isChecked); driftGraph->replot(); } void Guide::toggleRACorrectionsPlot(bool isChecked) { Options::setRACorrDisplayedOnGuideGraph(isChecked); driftGraph->graph(4)->setVisible(isChecked); updateCorrectionsScaleVisibility(); } void Guide::toggleDECorrectionsPlot(bool isChecked) { Options::setDECorrDisplayedOnGuideGraph(isChecked); driftGraph->graph(5)->setVisible(isChecked); updateCorrectionsScaleVisibility(); } void Guide::updateCorrectionsScaleVisibility() { bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph()); driftGraph->yAxis2->setVisible(isVisible); correctionSlider->setVisible(isVisible); driftGraph->replot(); } void Guide::setCorrectionGraphScale() { driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower * correctionSlider->value(), driftGraph->yAxis->range().upper * correctionSlider->value()); driftGraph->replot(); } void Guide::exportGuideData() { int numPoints = driftGraph->graph(0)->dataCount(); if (numPoints == 0) return; QUrl exportFile = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Export Guide Data"), guideURLPath, "CSV File (*.csv)"); if (exportFile.isEmpty()) // if user presses cancel return; if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false) exportFile.setPath(exportFile.toLocalFile() + ".csv"); QString path = exportFile.toLocalFile(); if (QFile::exists(path)) { int r = KMessageBox::warningContinueCancel(nullptr, i18n("A file named \"%1\" already exists. " "Overwrite it?", exportFile.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } if (!exportFile.isValid()) { QString message = i18n("Invalid URL: %1", exportFile.url()); KSNotification::sorry(message, i18n("Invalid URL")); return; } QFile file; file.setFileName(path); if (!file.open(QIODevice::WriteOnly)) { QString message = i18n("Unable to write to file %1", path); KSNotification::sorry(message, i18n("Could Not Open File")); return; } QTextStream outstream(&file); outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; for (int i = 0; i < numPoints; i++) { double t = driftGraph->graph(0)->dataMainKey(i); double ra = driftGraph->graph(0)->dataMainValue(i); double de = driftGraph->graph(1)->dataMainValue(i); double raPulse = driftGraph->graph(4)->dataMainValue(i); double dePulse = driftGraph->graph(5)->dataMainValue(i); QTime localTime = guideTimer; localTime = localTime.addSecs(t); outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' << dePulse << ',' << endl; } appendLogText(i18n("Guide Data Saved as: %1", path)); file.close(); } QString Guide::setRecommendedExposureValues(QList values) { exposureIN->setRecommendedValues(values); return exposureIN->getRecommendedValuesString(); } void Guide::addCamera(ISD::GDInterface *newCCD) { ISD::CCD *ccd = static_cast(newCCD); if (CCDs.contains(ccd)) return; if(guiderType != GUIDE_INTERNAL) { connect(ccd, &ISD::CCD::newBLOBManager, [ccd, this](INDI::Property * prop) { if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) { ccd->setBLOBEnabled(false); //This will disable PHD2 external guide frames until it is properly connected. currentCCD = ccd; } }); guiderCombo->clear(); guiderCombo->setEnabled(false); if (guiderType == GUIDE_PHD2) guiderCombo->addItem("PHD2"); else guiderCombo->addItem("LinGuider"); } else { guiderCombo->setEnabled(true); guiderCombo->addItem(ccd->getDeviceName()); } CCDs.append(ccd); checkCCD(); configurePHD2Camera(); } void Guide::configurePHD2Camera() { //Maybe something like this can be done for Linguider? //But for now, Linguider doesn't support INDI Cameras if(guiderType != GUIDE_PHD2) return; //This prevents a crash if phd2guider is null if(!phd2Guider) return; //This way it doesn't check if the equipment isn't connected yet. //It will check again when the equipment is connected. if(!phd2Guider->isConnected()) return; //This way it doesn't check if the equipment List has not been received yet. //It will ask for the list. When the list is received it will check again. if(phd2Guider->getCurrentCamera() == "") { phd2Guider->requestCurrentEquipmentUpdate(); return; } //this checks to see if a CCD in the list matches the name of PHD2's camera ISD::CCD *ccdMatch = nullptr; QString currentPHD2CameraName = "None"; foreach(ISD::CCD *ccd, CCDs) { if(phd2Guider->getCurrentCamera().contains(ccd->getDeviceName())) { ccdMatch = ccd; currentPHD2CameraName = (phd2Guider->getCurrentCamera()); break; } } //If this method gives the same result as last time, no need to update the Camera info again. //That way the user doesn't see a ton of messages printing about the PHD2 external camera. //But lets make sure the blob is set correctly every time. if(lastPHD2CameraName == currentPHD2CameraName) { setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); return; } //This means that a Guide Camera was connected before but it changed. if(currentCCD) setExternalGuiderBLOBEnabled(false); //Updating the currentCCD currentCCD = ccdMatch; //This updates the last camera name for the next time it is checked. lastPHD2CameraName = currentPHD2CameraName; //This sets a boolean that allows you to tell if the PHD2 camera is in Ekos phd2Guider->setCurrentCameraIsNotInEkos(currentCCD == nullptr); if(phd2Guider->isCurrentCameraNotInEkos()) { appendLogText(i18n("PHD2's current camera: %1, is NOT connected to Ekos. The PHD2 Guide Star Image will be received, but the full external guide frames cannot.", phd2Guider->getCurrentCamera())); subFrameCheck->setEnabled(false); //We don't want to actually change the user's subFrame Setting for when a camera really is connected, just check the box to tell the user. disconnect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); subFrameCheck->setChecked(true); return; } appendLogText(i18n("PHD2's current camera: %1, IS connected to Ekos. You can select whether to use the full external guide frames or just receive the PHD2 Guide Star Image using the SubFrame checkbox.", phd2Guider->getCurrentCamera())); subFrameCheck->setEnabled(true); connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); subFrameCheck->setChecked(Options::guideSubframeEnabled()); } void Guide::addGuideHead(ISD::GDInterface *newCCD) { if (guiderType != GUIDE_INTERNAL) return; ISD::CCD *ccd = static_cast(newCCD); CCDs.append(ccd); QString guiderName = ccd->getDeviceName() + QString(" Guider"); if (guiderCombo->findText(guiderName) == -1) { guiderCombo->addItem(guiderName); //CCDs.append(static_cast (newCCD)); } //checkCCD(CCDs.count()-1); //guiderCombo->setCurrentIndex(CCDs.count()-1); //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2); } void Guide::setTelescope(ISD::GDInterface *newTelescope) { currentTelescope = dynamic_cast(newTelescope); syncTelescopeInfo(); } bool Guide::setCamera(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; for (int i = 0; i < guiderCombo->count(); i++) if (device == guiderCombo->itemText(i)) { guiderCombo->setCurrentIndex(i); checkCCD(i); return true; } return false; } QString Guide::camera() { if (currentCCD) return currentCCD->getDeviceName(); return QString(); } void Guide::checkCCD(int ccdNum) { if (guiderType != GUIDE_INTERNAL) return; if (ccdNum == -1) { ccdNum = guiderCombo->currentIndex(); if (ccdNum == -1) return; } if (ccdNum <= CCDs.count()) { currentCCD = CCDs.at(ccdNum); if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) useGuideHead = true; else useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip && targetChip->isCapturing()) return; if (guiderType != GUIDE_INTERNAL) { syncCCDInfo(); return; } //connect(currentCCD, SIGNAL(FITSViewerClosed()), this, &Ekos::Guide::viewerClosed()), Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::numberUpdated, this, &Ekos::Guide::processCCDNumber, Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection); // 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 targetChip->setImageView(guideView, FITS_GUIDE); syncCCDInfo(); } } void Guide::syncCCDInfo() { INumberVectorProperty *nvp = nullptr; if (currentCCD == nullptr) return; if (useGuideHead) nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); else nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); if (nvp) { INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); if (np) ccdPixelSizeX = np->value; np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); if (np) ccdPixelSizeY = np->value; np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); if (np) ccdPixelSizeY = np->value; } updateGuideParams(); } void Guide::setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture) { if (primaryFocalLength > 0) focal_length = primaryFocalLength; if (primaryAperture > 0) aperture = primaryAperture; // If we have guide scope info, always prefer that over primary if (guideFocalLength > 0) focal_length = guideFocalLength; if (guideAperture > 0) aperture = guideAperture; updateGuideParams(); } void Guide::syncTelescopeInfo() { if (currentTelescope == nullptr || currentTelescope->isConnected() == false) return; INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); if (nvp) { INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); if (np && np->value > 0) primaryAperture = np->value; np = IUFindNumber(nvp, "GUIDER_APERTURE"); if (np && np->value > 0) guideAperture = np->value; aperture = primaryAperture; //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) aperture = guideAperture; np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH"); if (np && np->value > 0) primaryFL = np->value; np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH"); if (np && np->value > 0) guideFL = np->value; focal_length = primaryFL; //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) focal_length = guideFL; } updateGuideParams(); } void Guide::updateGuideParams() { if (currentCCD == nullptr) return; if (currentCCD->hasGuideHead() == false) useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip == nullptr) { appendLogText(i18n("Connection to the guide CCD is lost.")); return; } if (targetChip->getFrameType() != FRAME_LIGHT) return; if(guiderType == GUIDE_INTERNAL) binningCombo->setEnabled(targetChip->canBin()); int subBinX = 1, subBinY = 1; if (targetChip->canBin()) { int maxBinX, maxBinY; targetChip->getBinning(&subBinX, &subBinY); targetChip->getMaxBin(&maxBinX, &maxBinY); binningCombo->blockSignals(true); binningCombo->clear(); for (int i = 1; i <= maxBinX; i++) binningCombo->addItem(QString("%1x%2").arg(i).arg(i)); binningCombo->setCurrentIndex(subBinX - 1); binningCombo->blockSignals(false); } if (frameSettings.contains(targetChip) == false) { int x, y, w, h; if (targetChip->getFrame(&x, &y, &w, &h)) { if (w > 0 && h > 0) { int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); QVariantMap settings; settings["x"] = Options::guideSubframeEnabled() ? x : minX; settings["y"] = Options::guideSubframeEnabled() ? y : minY; settings["w"] = Options::guideSubframeEnabled() ? w : maxW; settings["h"] = Options::guideSubframeEnabled() ? h : maxH; settings["binx"] = subBinX; settings["biny"] = subBinY; frameSettings[targetChip] = settings; } } } if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1) { FOVScopeCombo->setItemData( ISD::CCD::TELESCOPE_PRIMARY, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(primaryFL / primaryAperture, 'f', 1), QString::number(primaryFL, 'f', 2), QString::number(primaryAperture, 'f', 2)), Qt::ToolTipRole); FOVScopeCombo->setItemData( ISD::CCD::TELESCOPE_GUIDE, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(guideFL / guideAperture, 'f', 1), QString::number(guideFL, 'f', 2), QString::number(guideAperture, 'f', 2)), Qt::ToolTipRole); guider->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, aperture, focal_length); emit guideChipUpdated(targetChip); int x, y, w, h; if (targetChip->getFrame(&x, &y, &w, &h)) { guider->setFrameParams(x, y, w, h, subBinX, subBinY); } l_Focal->setText(QString::number(focal_length, 'f', 1)); l_Aperture->setText(QString::number(aperture, 'f', 1)); if (aperture == 0) { l_FbyD->setText("0"); // Pixel scale in arcsec/pixel pixScaleX = 0; pixScaleY = 0; } else { l_FbyD->setText(QString::number(focal_length / aperture, 'f', 1)); // Pixel scale in arcsec/pixel pixScaleX = 206264.8062470963552 * ccdPixelSizeX / 1000.0 / focal_length; pixScaleY = 206264.8062470963552 * ccdPixelSizeY / 1000.0 / focal_length; } // FOV in arcmin double fov_w = (w * pixScaleX) / 60.0; double fov_h = (h * pixScaleY) / 60.0; l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); } } void Guide::addST4(ISD::ST4 *newST4) { if (guiderType != GUIDE_INTERNAL) return; foreach (ISD::ST4 *guidePort, ST4List) { if (!strcmp(guidePort->getDeviceName(), newST4->getDeviceName())) return; } ST4List.append(newST4); ST4Combo->addItem(newST4->getDeviceName()); setST4(0); } bool Guide::setST4(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; for (int i = 0; i < ST4List.count(); i++) if (ST4List.at(i)->getDeviceName() == device) { ST4Combo->setCurrentIndex(i); setST4(i); return true; } return false; } QString Guide::st4() { if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) return QString(); return ST4Combo->currentText(); } void Guide::setST4(int index) { if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) return; ST4Driver = ST4List.at(index); GuideDriver = ST4Driver; } void Guide::setAO(ISD::ST4 *newAO) { AODriver = newAO; //guider->setAO(true); } bool Guide::capture() { buildOperationStack(GUIDE_CAPTURE); return executeOperationStack(); } bool Guide::captureOneFrame() { captureTimeout.stop(); if (currentCCD == nullptr) return false; if (currentCCD->isConnected() == false) { appendLogText(i18n("Error: lost connection to CCD.")); return false; } // If CCD Telescope Type does not match desired scope type, change it if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex()) currentCCD->setTelescopeType(static_cast(FOVScopeCombo->currentIndex())); double seqExpose = exposureIN->value(); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->setCaptureMode(FITS_GUIDE); targetChip->setFrameType(FRAME_LIGHT); if (darkFrameCheck->isChecked()) targetChip->setCaptureFilter(FITS_NONE); else targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); guideView->setBaseSize(guideWidget->size()); setBusy(true); if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt()); } #if 0 switch (state) { 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; default: break; } #endif currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection); qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; double finalExposure = seqExpose; // Increase exposure for calibration frame if we need auto-select a star // To increase chances we detect one. if (operationStack.contains(GUIDE_STAR_SELECT) && Options::guideAutoStarEnabled()) finalExposure *= 3; // Timeout is exposure duration + timeout threshold in seconds captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD); targetChip->capture(finalExposure); return true; } bool Guide::abort() { if (currentCCD && guiderType == GUIDE_INTERNAL) { captureTimeout.stop(); pulseTimer.stop(); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip->isCapturing()) targetChip->abortExposure(); } manualDitherB->setEnabled(false); setBusy(false); switch (state) { case GUIDE_IDLE: case GUIDE_CONNECTED: case GUIDE_DISCONNECTED: break; case GUIDE_CALIBRATING: case GUIDE_DITHERING: case GUIDE_STAR_SELECT: case GUIDE_CAPTURE: case GUIDE_GUIDING: case GUIDE_LOOPING: guider->abort(); break; default: break; } return true; } void Guide::setBusy(bool enable) { if (enable && pi->isAnimated()) return; else if (enable == false && pi->isAnimated() == false) return; if (enable) { clearCalibrationB->setEnabled(false); guideB->setEnabled(false); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); stopB->setEnabled(true); pi->startAnimation(); //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); } else { if(guiderType != GUIDE_LINGUIDER) { captureB->setEnabled(true); loopB->setEnabled(true); autoStarCheck->setEnabled(true); if(currentCCD) subFrameCheck->setEnabled(true); } if (guiderType == GUIDE_INTERNAL) darkFrameCheck->setEnabled(true); if (calibrationComplete) clearCalibrationB->setEnabled(true); guideB->setEnabled(true); stopB->setEnabled(false); pi->stopAnimation(); connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); } } void Guide::processCaptureTimeout() { captureTimeoutCounter++; 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.")); abort(); return; } 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::newFITS(IBLOB *bp) { INDI_UNUSED(bp); captureTimeout.stop(); captureTimeoutCounter = 0; disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); int subBinX = 1, subBinY = 1; targetChip->getBinning(&subBinX, &subBinY); if (starCenter.x() == 0 && starCenter.y() == 0) { int x = 0, y = 0, w = 0, h = 0; if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; x = settings["x"].toInt(); y = settings["y"].toInt(); w = settings["w"].toInt(); h = settings["h"].toInt(); } else targetChip->getFrame(&x, &y, &w, &h); starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinY)); starCenter.setZ(subBinX); } syncTrackingBoxPosition(); setCaptureComplete(); } void Guide::setCaptureComplete() { DarkLibrary::Instance()->disconnect(this); if (operationStack.isEmpty() == false) { executeOperationStack(); return; } switch (state) { case GUIDE_IDLE: case GUIDE_ABORTED: case GUIDE_CONNECTED: case GUIDE_DISCONNECTED: case GUIDE_CALIBRATION_SUCESS: case GUIDE_CALIBRATION_ERROR: case GUIDE_DITHERING_ERROR: setBusy(false); break; case GUIDE_CAPTURE: state = GUIDE_IDLE; emit newStatus(state); setBusy(false); break; case GUIDE_LOOPING: capture(); break; case GUIDE_CALIBRATING: guider->calibrate(); break; case GUIDE_GUIDING: guider->guide(); break; case GUIDE_DITHERING: guider->dither(Options::ditherPixels()); break; // Feature only of internal guider case GUIDE_MANUAL_DITHERING: dynamic_cast(guider)->processManualDithering(); break; case GUIDE_REACQUIRE: guider->reacquire(); break; case GUIDE_DITHERING_SETTLE: if (Options::ditherNoGuiding()) return; capture(); break; default: break; } emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } void Guide::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS_GUIDE) << text; emit newLog(text); } void Guide::clearLog() { m_LogText.clear(); emit newLog(QString()); } void Guide::setDECSwap(bool enable) { if (ST4Driver == nullptr || guider == nullptr) return; if (guiderType == GUIDE_INTERNAL) { dynamic_cast(guider)->setDECSwap(enable); ST4Driver->setDECSwap(enable); } } bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) { if (GuideDriver == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR)) return false; if (state == GUIDE_CALIBRATING) pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100); return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); } bool Guide::sendPulse(GuideDirection dir, int msecs) { if (GuideDriver == nullptr || dir == NO_DIR) return false; if (state == GUIDE_CALIBRATING) pulseTimer.start(msecs + 100); return GuideDriver->doPulse(dir, msecs); } QStringList Guide::getST4Devices() { QStringList devices; foreach (ISD::ST4 *driver, ST4List) devices << driver->getDeviceName(); return devices; } #if 0 void Guide::processRapidStarData(ISD::CCDChip * targetChip, double dx, double dy, double fit) { // Check if guide star is lost if (dx == -1 && dy == -1 && fit == -1) { KSNotification::error(i18n("Lost track of the guide star. Rapid guide aborted.")); guider->abort(); return; } FITSView * targetImage = targetChip->getImage(FITS_GUIDE); if (targetImage == nullptr) { pmath->setImageView(nullptr); guider->setImageView(nullptr); calibration->setImageView(nullptr); } if (rapidGuideReticleSet == 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; } pmath->setRapidStarData(dx, dy); if (guider->isDithering()) { pmath->performProcessing(); if (guider->dither() == false) { appendLogText(i18n("Dithering failed. Autoguiding aborted.")); emit newStatus(GUIDE_DITHERING_ERROR); guider->abort(); //emit ditherFailed(); } } else { guider->guide(); capture(); } } void Guide::startRapidGuide() { ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (currentCCD->setRapidGuide(targetChip, true) == false) { appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting...")); guider->abort(); return; } rapidGuideReticleSet = false; 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::stopRapidGuide() { ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); pmath->setRapidGuide(false); rapidGuideReticleSet = false; currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double))); currentCCD->configureRapidGuide(targetChip, false, false, false); currentCCD->setRapidGuide(targetChip, false); } #endif bool Guide::calibrate() { // Set status to idle and let the operations change it as they get executed state = GUIDE_IDLE; emit newStatus(state); if (guiderType == GUIDE_INTERNAL) { ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (frameSettings.contains(targetChip)) { targetChip->resetFrame(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); QVariantMap settings = frameSettings[targetChip]; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; frameSettings[targetChip] = settings; subFramed = false; } } saveSettings(); buildOperationStack(GUIDE_CALIBRATING); executeOperationStack(); qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); return true; } bool Guide::guide() { auto executeGuide = [this]() { if(guiderType != GUIDE_PHD2) { if (calibrationComplete == false) { calibrate(); return; } } saveSettings(); guider->guide(); //If PHD2 gets a Guide command and it is looping, it will accept a lock position //but if it was not looping it will ignore the lock position and do an auto star automatically //This is not the default behavior in Ekos if auto star is not selected. //This gets around that by noting the position of the tracking box, and enforcing it after the state switches to guide. if(!Options::guideAutoStarEnabled()) { if(guiderType == GUIDE_PHD2 && guideView->isTrackingBoxEnabled()) { double x = starCenter.x(); double y = starCenter.y(); if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() > 50) { guideConnect = connect(this, &Guide::newStatus, this, [this, x, y](Ekos::GuideState newState) { if(newState == GUIDE_GUIDING) { phd2Guider->setLockPosition(x,y); disconnect(guideConnect); } }); } } } } }; if (Options::defaultCaptureCCD() == guiderCombo->currentText()) { connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeGuide]() { //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); KSMessageBox::Instance()->disconnect(this); executeGuide(); }); KSMessageBox::Instance()->questionYesNo(i18n("The guide camera is identical to the primary imaging camera. Are you sure you want to continue?")); return false; } executeGuide(); return true; } bool Guide::dither() { if (Options::ditherNoGuiding() && state == GUIDE_IDLE) { ditherDirectly(); return true; } if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) return true; //This adds a dither text item to the graph where dithering occurred. double time = guideTimer.elapsed() / 1000.0; QCPItemText *ditherLabel = new QCPItemText(driftGraph); ditherLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft); ditherLabel->position->setType(QCPItemPosition::ptPlotCoords); ditherLabel->position->setCoords(time, 1.5); ditherLabel->setColor(Qt::white); ditherLabel->setBrush(Qt::NoBrush); ditherLabel->setPen(Qt::NoPen); ditherLabel->setText("Dither"); ditherLabel->setFont(QFont(font().family(), 10)); if (guiderType == GUIDE_INTERNAL) { if (state != GUIDE_GUIDING) capture(); setStatus(GUIDE_DITHERING); return true; } else return guider->dither(Options::ditherPixels()); } bool Guide::suspend() { if (state == GUIDE_SUSPENDED) return true; else if (state >= GUIDE_CAPTURE) return guider->suspend(); else return false; } bool Guide::resume() { if (state == GUIDE_GUIDING) return true; else if (state == GUIDE_SUSPENDED) return guider->resume(); else return false; } void Guide::setCaptureStatus(CaptureState newState) { switch (newState) { case CAPTURE_DITHERING: dither(); break; default: break; } } void Guide::setPierSide(ISD::Telescope::PierSide newSide) { Q_UNUSED(newSide); // If pier side changes in internal guider // and calibration was already done // then let's swap if (guiderType == GUIDE_INTERNAL && state != GUIDE_GUIDING && state != GUIDE_CALIBRATING && calibrationComplete) { clearCalibration(); appendLogText(i18n("Pier side change detected. Clearing calibration.")); } } void Guide::setMountStatus(ISD::Telescope::Status newState) { // 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) { case ISD::Telescope::MOUNT_SLEWING: case ISD::Telescope::MOUNT_PARKING: case ISD::Telescope::MOUNT_MOVING: captureB->setEnabled(false); loopB->setEnabled(false); clearCalibrationB->setEnabled(false); break; default: if (pi->isAnimated() == false) { captureB->setEnabled(true); loopB->setEnabled(true); clearCalibrationB->setEnabled(true); } } } void Guide::setExposure(double value) { exposureIN->setValue(value); } void Guide::setImageFilter(const QString &value) { for (int i = 0; i < filterCombo->count(); i++) if (filterCombo->itemText(i) == value) { filterCombo->setCurrentIndex(i); break; } } void Guide::setCalibrationTwoAxis(bool enable) { Options::setTwoAxisEnabled(enable); } void Guide::setCalibrationAutoStar(bool enable) { autoStarCheck->setChecked(enable); } void Guide::setCalibrationAutoSquareSize(bool enable) { Options::setGuideAutoSquareSizeEnabled(enable); } void Guide::setCalibrationPulseDuration(int pulseDuration) { Options::setCalibrationPulseDuration(pulseDuration); } void Guide::setGuideBoxSizeIndex(int index) { Options::setGuideSquareSizeIndex(index); } void Guide::setGuideAlgorithmIndex(int index) { Options::setGuideAlgorithm(index); } void Guide::setSubFrameEnabled(bool enable) { Options::setGuideSubframeEnabled(enable); if (subFrameCheck->isChecked() != enable) subFrameCheck->setChecked(enable); if(guiderType == GUIDE_PHD2) setExternalGuiderBLOBEnabled(!enable); } #if 0 void Guide::setGuideRapidEnabled(bool enable) { //guider->setGuideOptions(guider->getAlgorithm(), guider->useSubFrame() , enable); } #endif void Guide::setDitherSettings(bool enable, double value) { Options::setDitherEnabled(enable); Options::setDitherPixels(value); } #if 0 void Guide::startAutoCalibrateGuide() { // A must for auto stuff Options::setGuideAutoStarEnabled(true); if (Options::resetGuideCalibration()) clearCalibration(); guide(); #if 0 if (guiderType == GUIDE_INTERNAL) { calibrationComplete = false; autoCalibrateGuide = true; calibrate(); } else { calibrationComplete = true; autoCalibrateGuide = true; guide(); } #endif } #endif void Guide::clearCalibration() { calibrationComplete = false; guider->clearCalibration(); appendLogText(i18n("Calibration is cleared.")); } void Guide::setStatus(Ekos::GuideState newState) { if (newState == state) { // pass through the aborted state if (newState == GUIDE_ABORTED) emit newStatus(state); return; } GuideState previousState = state; state = newState; emit newStatus(state); switch (state) { case GUIDE_CONNECTED: appendLogText(i18n("External guider connected.")); externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(true); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); if(guiderType == GUIDE_PHD2) { captureB->setEnabled(true); loopB->setEnabled(true); autoStarCheck->setEnabled(true); configurePHD2Camera(); setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); boxSizeCombo->setEnabled(true); } break; case GUIDE_DISCONNECTED: appendLogText(i18n("External guider disconnected.")); setBusy(false); //This needs to come before caputureB since it will set it to enabled again. externalConnectB->setEnabled(true); externalDisconnectB->setEnabled(false); clearCalibrationB->setEnabled(false); guideB->setEnabled(false); captureB->setEnabled(false); loopB->setEnabled(false); autoStarCheck->setEnabled(false); boxSizeCombo->setEnabled(false); //setExternalGuiderBLOBEnabled(true); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif break; case GUIDE_CALIBRATION_SUCESS: appendLogText(i18n("Calibration completed.")); calibrationComplete = true; /*if (autoCalibrateGuide) { autoCalibrateGuide = false; guide(); } else setBusy(false);*/ if(guiderType != GUIDE_PHD2) //PHD2 will take care of this. If this command is executed for PHD2, it might start guiding when it is first connected, if the calibration was completed already. guide(); break; case GUIDE_IDLE: case GUIDE_CALIBRATION_ERROR: setBusy(false); manualDitherB->setEnabled(false); break; case GUIDE_CALIBRATING: appendLogText(i18n("Calibration started.")); setBusy(true); break; case GUIDE_GUIDING: if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) appendLogText(i18n("Guiding resumed.")); else { appendLogText(i18n("Autoguiding started.")); setBusy(true); clearGuideGraphs(); guideTimer = QTime::currentTime(); refreshColorScheme(); } manualDitherB->setEnabled(true); break; case GUIDE_ABORTED: appendLogText(i18n("Autoguiding aborted.")); setBusy(false); break; case GUIDE_SUSPENDED: appendLogText(i18n("Guiding suspended.")); break; case GUIDE_REACQUIRE: capture(); break; case GUIDE_MANUAL_DITHERING: appendLogText(i18n("Manual dithering in progress.")); break; case GUIDE_DITHERING: appendLogText(i18n("Dithering in progress.")); break; case GUIDE_DITHERING_SETTLE: if (Options::ditherSettle() > 0) appendLogText(i18np("Post-dither settling for %1 second...", "Post-dither settling for %1 seconds...", Options::ditherSettle())); capture(); break; case GUIDE_DITHERING_ERROR: appendLogText(i18n("Dithering failed.")); // LinGuider guide continue after dithering failure if (guiderType != GUIDE_LINGUIDER) { //state = GUIDE_IDLE; state = GUIDE_ABORTED; setBusy(false); } break; case GUIDE_DITHERING_SUCCESS: appendLogText(i18n("Dithering completed successfully.")); // Go back to guiding state immediately if using regular guider if (Options::ditherNoGuiding() == false) { setStatus(GUIDE_GUIDING); // Only capture again if we are using internal guider if (guiderType == GUIDE_INTERNAL) capture(); } break; default: break; } } void Guide::updateCCDBin(int index) { if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL) return; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->setBinning(index + 1, index + 1); QVariantMap settings = frameSettings[targetChip]; settings["binx"] = index + 1; settings["biny"] = index + 1; frameSettings[targetChip] = settings; guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), settings["binx"].toInt(), settings["biny"].toInt()); } void Guide::processCCDNumber(INumberVectorProperty *nvp) { if (currentCCD == nullptr || strcmp(nvp->device, currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) return; if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead)) { binningCombo->disconnect(); binningCombo->setCurrentIndex(nvp->np[0].value - 1); connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin); } } void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) { if (guiderType != GUIDE_INTERNAL) return; INDI_UNUSED(exposure); if (expState == IPS_ALERT && ((state == GUIDE_GUIDING) || (state == GUIDE_DITHERING) || (state == GUIDE_CALIBRATING))) { appendLogText(i18n("Exposure failed. Restarting exposure...")); currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); targetChip->capture(exposureIN->value()); } } void Guide::setDarkFrameEnabled(bool enable) { Options::setGuideDarkFrameEnabled(enable); if (darkFrameCheck->isChecked() != enable) darkFrameCheck->setChecked(enable); } void Guide::saveDefaultGuideExposure() { Options::setGuideExposure(exposureIN->value()); if(guiderType == GUIDE_PHD2) phd2Guider->requestSetExposureTime(exposureIN->value() * 1000); } void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) { starCenter.setX(newCenter.x()); starCenter.setY(newCenter.y()); if (newCenter.z() > 0) starCenter.setZ(newCenter.z()); if (updateNow) syncTrackingBoxPosition(); } void Guide::syncTrackingBoxPosition() { if(!currentCCD || guiderType == GUIDE_LINGUIDER) return; if(guiderType == GUIDE_PHD2) { //This way it won't set the tracking box on the Guide Star Image. if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() < 50) { guideView->setTrackingBoxEnabled(false); return; } } } ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); Q_ASSERT(targetChip); int subBinX = 1, subBinY = 1; targetChip->getBinning(&subBinX, &subBinY); if (starCenter.isNull() == false) { double boxSize = boxSizeCombo->currentText().toInt(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); // If box size is larger than image size, set it to lower index if (boxSize / subBinX >= w || boxSize / subBinY >= h) { int newIndex = boxSizeCombo->currentIndex() - 1; if (newIndex >= 0) boxSizeCombo->setCurrentIndex(newIndex); return; } // If binning changed, update coords accordingly if (subBinX != starCenter.z()) { if (starCenter.z() > 0) { starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); } starCenter.setZ(subBinX); } QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), boxSize / subBinX, boxSize / subBinY); guideView->setTrackingBoxEnabled(true); guideView->setTrackingBox(starRect); } } bool Guide::setGuiderType(int type) { // Use default guider option if (type == -1) type = Options::guiderType(); else if (type == guiderType) return true; if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING) { appendLogText(i18n("Cannot change guider type while active.")); return false; } if (guider != nullptr) { // Disconnect from host if (guider->isConnected()) guider->Disconnect(); // Disconnect signals guider->disconnect(); } guiderType = static_cast(type); switch (type) { case GUIDE_INTERNAL: { connect(internalGuider, SIGNAL(newPulse(GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int))); connect(internalGuider, SIGNAL(newPulse(GuideDirection, int, GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int, GuideDirection, int))); connect(internalGuider, SIGNAL(DESwapChanged(bool)), swapCheck, SLOT(setChecked(bool))); connect(internalGuider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); guider = internalGuider; internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); internalGuider->setRegionAxis(opsGuide->kcfg_GuideRegionAxis->currentText().toInt()); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); captureB->setEnabled(true); loopB->setEnabled(true); darkFrameCheck->setEnabled(true); subFrameCheck->setEnabled(true); autoStarCheck->setEnabled(true); guiderCombo->setEnabled(true); ST4Combo->setEnabled(true); exposureIN->setEnabled(true); binningCombo->setEnabled(true); boxSizeCombo->setEnabled(true); filterCombo->setEnabled(true); externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(false); controlGroup->setEnabled(true); infoGroup->setEnabled(true); label_6->setEnabled(true); FOVScopeCombo->setEnabled(true); l_3->setEnabled(true); spinBox_GuideRate->setEnabled(true); l_RecommendedGain->setEnabled(true); l_5->setEnabled(true); l_6->setEnabled(true); l_7->setEnabled(true); l_8->setEnabled(true); l_Aperture->setEnabled(true); l_FOV->setEnabled(true); l_FbyD->setEnabled(true); l_Focal->setEnabled(true); driftGraphicsGroup->setEnabled(true); guiderCombo->setToolTip(i18n("Select guide camera.")); updateGuideParams(); } break; case GUIDE_PHD2: if (phd2Guider.isNull()) phd2Guider = new PHD2(); guider = phd2Guider; phd2Guider->setGuideView(guideView); connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); clearCalibrationB->setEnabled(true); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); guideB->setEnabled(false); //This will be enabled later when equipment connects (or not) externalConnectB->setEnabled(false); checkBox_DirRA->setEnabled(false); eastControlCheck->setEnabled(false); westControlCheck->setEnabled(false); swapCheck->setEnabled(false); controlGroup->setEnabled(false); infoGroup->setEnabled(true); label_6->setEnabled(false); FOVScopeCombo->setEnabled(false); l_3->setEnabled(false); spinBox_GuideRate->setEnabled(false); l_RecommendedGain->setEnabled(false); l_5->setEnabled(false); l_6->setEnabled(false); l_7->setEnabled(false); l_8->setEnabled(false); l_Aperture->setEnabled(false); l_FOV->setEnabled(false); l_FbyD->setEnabled(false); l_Focal->setEnabled(false); driftGraphicsGroup->setEnabled(true); ST4Combo->setEnabled(false); exposureIN->setEnabled(true); binningCombo->setEnabled(false); boxSizeCombo->setEnabled(false); filterCombo->setEnabled(false); guiderCombo->setEnabled(false); if (Options::resetGuideCalibration()) appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); updateGuideParams(); break; case GUIDE_LINGUIDER: if (linGuider.isNull()) linGuider = new LinGuider(); guider = linGuider; clearCalibrationB->setEnabled(true); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); guideB->setEnabled(true); externalConnectB->setEnabled(true); controlGroup->setEnabled(false); infoGroup->setEnabled(false); driftGraphicsGroup->setEnabled(false); ST4Combo->setEnabled(false); exposureIN->setEnabled(false); binningCombo->setEnabled(false); boxSizeCombo->setEnabled(false); filterCombo->setEnabled(false); guiderCombo->setEnabled(false); updateGuideParams(); break; } if (guider != nullptr) { connect(guider, &Ekos::GuideInterface::frameCaptureRequested, this, &Ekos::Guide::capture); connect(guider, &Ekos::GuideInterface::newLog, this, &Ekos::Guide::appendLogText); connect(guider, &Ekos::GuideInterface::newStatus, this, &Ekos::Guide::setStatus); connect(guider, &Ekos::GuideInterface::newStarPosition, this, &Ekos::Guide::setStarPosition); connect(guider, &Ekos::GuideInterface::newAxisDelta, this, &Ekos::Guide::setAxisDelta); connect(guider, &Ekos::GuideInterface::newAxisPulse, this, &Ekos::Guide::setAxisPulse); connect(guider, &Ekos::GuideInterface::newAxisSigma, this, &Ekos::Guide::setAxisSigma); connect(guider, &Ekos::GuideInterface::guideEquipmentUpdated, this, &Ekos::Guide::configurePHD2Camera); } externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(false); if (guider != nullptr && guiderType != GUIDE_INTERNAL) { externalConnectB->setEnabled(!guider->isConnected()); externalDisconnectB->setEnabled(guider->isConnected()); } if (guider != nullptr) guider->Connect(); return true; } void Guide::updateTrackingBoxSize(int currentIndex) { if (currentIndex >= 0) { Options::setGuideSquareSizeIndex(currentIndex); if (guiderType == GUIDE_INTERNAL) dynamic_cast(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt()); syncTrackingBoxPosition(); } } /* void Guide::onXscaleChanged( int i ) { int rx, ry; driftGraphics->getVisibleRanges( &rx, &ry ); driftGraphics->setVisibleRanges( i*driftGraphics->getGridN(), ry ); driftGraphics->update(); } void Guide::onYscaleChanged( int i ) { int rx, ry; driftGraphics->getVisibleRanges( &rx, &ry ); driftGraphics->setVisibleRanges( rx, i*driftGraphics->getGridN() ); driftGraphics->update(); } */ void Guide::onThresholdChanged(int index) { switch (guiderType) { case GUIDE_INTERNAL: dynamic_cast(guider)->setSquareAlgorithm(index); break; default: break; } } void Guide::onInfoRateChanged(double val) { Options::setGuidingRate(val); double gain = 0; if (val > 0.01) gain = 1000.0 / (val * 15.0); l_RecommendedGain->setText(i18n("P: %1", QString().setNum(gain, 'f', 2))); } void Guide::onEnableDirRA(bool enable) { Options::setRAGuideEnabled(enable); } void Guide::onEnableDirDEC(bool enable) { Options::setDECGuideEnabled(enable); updatePHD2Directions(); } void Guide::syncSettings() { QSpinBox *pSB = nullptr; QDoubleSpinBox *pDSB = nullptr; QCheckBox *pCB = nullptr; QObject *obj = sender(); if ((pSB = qobject_cast(obj))) { if (pSB == spinBox_MaxPulseRA) Options::setRAMaximumPulse(pSB->value()); else if (pSB == spinBox_MaxPulseDEC) Options::setDECMaximumPulse(pSB->value()); else if (pSB == spinBox_MinPulseRA) Options::setRAMinimumPulse(pSB->value()); else if (pSB == spinBox_MinPulseDEC) Options::setDECMinimumPulse(pSB->value()); } else if ((pDSB = qobject_cast(obj))) { if (pDSB == spinBox_PropGainRA) Options::setRAProportionalGain(pDSB->value()); else if (pDSB == spinBox_PropGainDEC) Options::setDECProportionalGain(pDSB->value()); else if (pDSB == spinBox_IntGainRA) Options::setRAIntegralGain(pDSB->value()); else if (pDSB == spinBox_IntGainDEC) Options::setDECIntegralGain(pDSB->value()); else if (pDSB == spinBox_DerGainRA) Options::setRADerivativeGain(pDSB->value()); else if (pDSB == spinBox_DerGainDEC) Options::setDECDerivativeGain(pDSB->value()); } else if ((pCB = qobject_cast(obj))) { if (pCB == autoStarCheck) Options::setGuideAutoStarEnabled(pCB->isChecked()); } } void Guide::onControlDirectionChanged(bool enable) { QObject *obj = sender(); if (northControlCheck == dynamic_cast(obj)) { Options::setNorthDECGuideEnabled(enable); updatePHD2Directions(); } else if (southControlCheck == dynamic_cast(obj)) { Options::setSouthDECGuideEnabled(enable); updatePHD2Directions(); } else if (westControlCheck == dynamic_cast(obj)) { Options::setWestRAGuideEnabled(enable); } else if (eastControlCheck == dynamic_cast(obj)) { Options::setEastRAGuideEnabled(enable); } } void Guide::updatePHD2Directions() { if(guiderType == GUIDE_PHD2) phd2Guider -> requestSetDEGuideMode(checkBox_DirDEC->isChecked(), northControlCheck->isChecked(), southControlCheck->isChecked()); } void Guide::updateDirectionsFromPHD2(QString mode) { //disable connections disconnect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); disconnect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); disconnect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); if(mode == "Auto") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(true); southControlCheck->setChecked(true); } else if(mode == "North") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(true); southControlCheck->setChecked(false); } else if(mode == "South") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(false); southControlCheck->setChecked(true); } else //Off { checkBox_DirDEC->setChecked(false); northControlCheck->setChecked(true); southControlCheck->setChecked(true); } //Re-enable connections connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); } #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()); // Autostar autoStarCheck->setChecked(Options::guideAutoStarEnabled()); } void Guide::saveSettings() { // Exposure Options::setGuideExposure(exposureIN->value()); // Box Size Options::setGuideSquareSizeIndex(boxSizeCombo->currentIndex()); // Dark frame? Options::setGuideDarkFrameEnabled(darkFrameCheck->isChecked()); // Subframed? Options::setGuideSubframeEnabled(subFrameCheck->isChecked()); // Guiding Rate? Options::setGuidingRate(spinBox_GuideRate->value()); // RA/DEC enabled? Options::setRAGuideEnabled(checkBox_DirRA->isChecked()); Options::setDECGuideEnabled(checkBox_DirDEC->isChecked()); // N/S enabled? Options::setNorthDECGuideEnabled(northControlCheck->isChecked()); Options::setSouthDECGuideEnabled(southControlCheck->isChecked()); // W/E enabled? Options::setWestRAGuideEnabled(westControlCheck->isChecked()); Options::setEastRAGuideEnabled(eastControlCheck->isChecked()); // PID Control - Proportional Gain Options::setRAProportionalGain(spinBox_PropGainRA->value()); Options::setDECProportionalGain(spinBox_PropGainDEC->value()); // PID Control - Integral Gain Options::setRAIntegralGain(spinBox_IntGainRA->value()); Options::setDECIntegralGain(spinBox_IntGainDEC->value()); // PID Control - Derivative Gain Options::setRADerivativeGain(spinBox_DerGainRA->value()); Options::setDECDerivativeGain(spinBox_DerGainDEC->value()); // Max Pulse Duration (ms) Options::setRAMaximumPulse(spinBox_MaxPulseRA->value()); Options::setDECMaximumPulse(spinBox_MaxPulseDEC->value()); // Min Pulse Duration (ms) Options::setRAMinimumPulse(spinBox_MinPulseRA->value()); Options::setDECMinimumPulse(spinBox_MinPulseDEC->value()); } void Guide::setTrackingStar(int x, int y) { QVector3D newStarPosition(x, y, -1); setStarPosition(newStarPosition, true); if(guiderType == GUIDE_PHD2) { //The Guide Star Image is 32 pixels across or less, so this guarantees it isn't that. if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() > 50) phd2Guider->setLockPosition(starCenter.x(),starCenter.y()); } } /*if (state == GUIDE_STAR_SELECT) { guider->setStarPosition(newStarPosition); guider->calibrate(); }*/ if (operationStack.isEmpty() == false) executeOperationStack(); } void Guide::setAxisDelta(double ra, double de) { //If PHD2 starts guiding because somebody pusted the button remotely, we want to set the state to guiding. //If guide pulses start coming in, it must be guiding. if(guiderType == GUIDE_PHD2 && state != GUIDE_GUIDING) setStatus(GUIDE_GUIDING); // Time since timer started. double key = guideTimer.elapsed() / 1000.0; ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. driftGraph->graph(0)->addData(key, ra); driftGraph->graph(1)->addData(key, de); int currentNumPoints = driftGraph->graph(0)->dataCount(); guideSlider->setMaximum(currentNumPoints); if(graphOnLatestPt) guideSlider->setValue(currentNumPoints); // Expand range if it doesn't fit already if (driftGraph->yAxis->range().contains(ra) == false) driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); if (driftGraph->yAxis->range().contains(de) == false) driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); // Show last 120 seconds //driftGraph->xAxis->setRange(key, 120, Qt::AlignRight); if(graphOnLatestPt) { driftGraph->xAxis->setRange(key, driftGraph->xAxis->range().size(), Qt::AlignRight); driftGraph->graph(2)->data()->clear(); //Clear highlighted RA point driftGraph->graph(3)->data()->clear(); //Clear highlighted DEC point driftGraph->graph(2)->addData(key, ra); //Set highlighted RA point to latest point driftGraph->graph(3)->addData(key, de); //Set highlighted DEC point to latest point } driftGraph->replot(); //Add to Drift Plot driftPlot->graph(0)->addData(ra, de); if(graphOnLatestPt) { driftPlot->graph(1)->data()->clear(); //Clear highlighted point driftPlot->graph(1)->addData(ra, de); //Set highlighted point to latest point } if (driftPlot->xAxis->range().contains(ra) == false || driftPlot->yAxis->range().contains(de) == false) { driftPlot->setBackground(QBrush(Qt::gray)); QTimer::singleShot(300, this, [ = ]() { driftPlot->setBackground(QBrush(Qt::black)); driftPlot->replot(); }); } driftPlot->replot(); l_DeltaRA->setText(QString::number(ra, 'f', 2)); l_DeltaDEC->setText(QString::number(de, 'f', 2)); emit newAxisDelta(ra, de); profilePixmap = driftGraph->grab(); emit newProfilePixmap(profilePixmap); } void Guide::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); } QList Guide::axisDelta() { QList delta; delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); return delta; } QList Guide::axisSigma() { QList sigma; sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); return sigma; } void Guide::setAxisPulse(double ra, double de) { l_PulseRA->setText(QString::number(static_cast(ra))); l_PulseDEC->setText(QString::number(static_cast(de))); double key = guideTimer.elapsed() / 1000.0; driftGraph->graph(4)->addData(key, ra); driftGraph->graph(5)->addData(key, de); } void Guide::refreshColorScheme() { // Drift color legend if (driftGraph) { if (driftGraph->graph(0) && driftGraph->graph(1) && driftGraph->graph(2) && driftGraph->graph(3) && driftGraph->graph(4) && driftGraph->graph(5)) { driftGraph->graph(0)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(1)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(2)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10)); driftGraph->graph(3)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10)); QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); raPulseColor.setAlpha(75); driftGraph->graph(4)->setPen(QPen(raPulseColor)); driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); dePulseColor.setAlpha(75); driftGraph->graph(5)->setPen(QPen(dePulseColor)); driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); } } } void Guide::driftMouseClicked(QMouseEvent *event) { if (event->buttons() & Qt::RightButton) { driftGraph->yAxis->setRange(-3, 3); } } void Guide::driftMouseOverLine(QMouseEvent *event) { double key = driftGraph->xAxis->pixelToCoord(event->localPos().x()); if (driftGraph->xAxis->range().contains(key)) { QCPGraph *graph = qobject_cast(driftGraph->plottableAt(event->pos(), false)); if (graph) { int raIndex = driftGraph->graph(0)->findBegin(key); int deIndex = driftGraph->graph(1)->findBegin(key); double raDelta = driftGraph->graph(0)->dataMainValue(raIndex); double deDelta = driftGraph->graph(1)->dataMainValue(deIndex); double raPulse = driftGraph->graph(4)->dataMainValue(raIndex); //Get RA Pulse from RA pulse data double dePulse = driftGraph->graph(5)->dataMainValue(deIndex); //Get DEC Pulse from DEC pulse data // Compute time value: QTime localTime = guideTimer; localTime = localTime.addSecs(key); QToolTip::hideText(); if(raPulse == 0 && dePulse == 0) { QToolTip::showText( event->globalPos(), i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds;", "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
", localTime.toString("hh:mm:ss AP"), QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2))); } else { QToolTip::showText( event->globalPos(), i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is RA Pulse in ms; %5 is DE Pulse in ms", "" "" "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
RA Pulse: %4 ms
DE Pulse: %5 ms
", localTime.toString("hh:mm:ss AP"), QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2), QString::number(raPulse, 'f', 2), QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph. } } else QToolTip::hideText(); driftGraph->replot(); } } void Guide::buildOperationStack(GuideState operation) { operationStack.clear(); switch (operation) { case GUIDE_CAPTURE: if (Options::guideDarkFrameEnabled()) operationStack.push(GUIDE_DARK); operationStack.push(GUIDE_CAPTURE); operationStack.push(GUIDE_SUBFRAME); break; case GUIDE_CALIBRATING: operationStack.push(GUIDE_CALIBRATING); if (guiderType == GUIDE_INTERNAL) { if (Options::guideDarkFrameEnabled()) operationStack.push(GUIDE_DARK); // Auto Star Selected Path if (Options::guideAutoStarEnabled()) { // If subframe is enabled and we need to auto select a star, then we need to make the final capture // of the subframed image. This is only done if we aren't already subframed. if (subFramed == false && Options::guideSubframeEnabled()) operationStack.push(GUIDE_CAPTURE); // Do not subframe and auto-select star on Image Guiding mode if (Options::imageGuidingEnabled() == false) { operationStack.push(GUIDE_SUBFRAME); operationStack.push(GUIDE_STAR_SELECT); } operationStack.push(GUIDE_CAPTURE); // If we are being ask to go full frame, let's do that first if (subFramed == true && Options::guideSubframeEnabled() == false) operationStack.push(GUIDE_SUBFRAME); } // Manual Star Selection Path else { // In Image Guiding, we never need to subframe if (Options::imageGuidingEnabled() == false) { // Final capture before we start calibrating if (subFramed == false && Options::guideSubframeEnabled()) operationStack.push(GUIDE_CAPTURE); // Subframe if required operationStack.push(GUIDE_SUBFRAME); } // First capture an image operationStack.push(GUIDE_CAPTURE); } } break; default: break; } } bool Guide::executeOperationStack() { if (operationStack.isEmpty()) return false; GuideState nextOperation = operationStack.pop(); bool actionRequired = false; switch (nextOperation) { case GUIDE_SUBFRAME: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_DARK: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_CAPTURE: actionRequired = captureOneFrame(); break; case GUIDE_STAR_SELECT: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_CALIBRATING: if (guiderType == GUIDE_INTERNAL) { guider->setStarPosition(starCenter); dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); // No need to calibrate if (Options::imageGuidingEnabled()) { setStatus(GUIDE_CALIBRATION_SUCESS); break; } // Tracking must be engaged if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) currentTelescope->setTrackEnabled(true); } if (guider->calibrate()) { if (guiderType == GUIDE_INTERNAL) disconnect(guideView, SIGNAL(trackingStarSelected(int, int)), this, SLOT(setTrackingStar(int, int))); setBusy(true); } else { emit newStatus(GUIDE_CALIBRATION_ERROR); state = GUIDE_IDLE; appendLogText(i18n("Calibration failed to start.")); setBusy(false); } break; default: break; } // If an additional action is required, return return and continue later if (actionRequired) return true; // Otherwise, continue processing the stack else return executeOperationStack(); } bool Guide::executeOneOperation(GuideState operation) { bool actionRequired = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); int subBinX, subBinY; targetChip->getBinning(&subBinX, &subBinY); switch (operation) { case GUIDE_SUBFRAME: { // Check if we need and can subframe if (subFramed == false && Options::guideSubframeEnabled() == true && targetChip->canSubframe()) { int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); int offset = boxSizeCombo->currentText().toInt() / subBinX; int x = starCenter.x(); int y = starCenter.y(); x = (x - offset * 2) * subBinX; y = (y - offset * 2) * subBinY; int w = offset * 4 * subBinX; int h = offset * 4 * subBinY; if (x < minX) x = minX; if (y < minY) y = minY; if ((x + w) > maxW) w = maxW - x; if ((y + h) > maxH) h = maxH - y; targetChip->setFrame(x, y, w, h); subFramed = true; QVariantMap settings = frameSettings[targetChip]; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; settings["binx"] = subBinX; settings["biny"] = subBinY; frameSettings[targetChip] = settings; starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinX)); } // Otherwise check if we are already subframed // and we need to go back to full frame // or if we need to go back to full frame since we need // to reaquire a star else if (subFramed && (Options::guideSubframeEnabled() == false || state == GUIDE_REACQUIRE)) { targetChip->resetFrame(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); QVariantMap settings; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; settings["binx"] = 1; settings["biny"] = 1; frameSettings[targetChip] = settings; subFramed = false; starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinX)); //starCenter.setX(0); //starCenter.setY(0); } } break; case GUIDE_DARK: { // Do we need to take a dark frame? if (Options::guideDarkFrameEnabled()) { QVariantMap settings = frameSettings[targetChip]; uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); FITSData *darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, [&](bool completed) { DarkLibrary::Instance()->disconnect(this); if (completed != darkFrameCheck->isChecked()) setDarkFrameEnabled(completed); if (completed) setCaptureComplete(); else abort(); }); connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Guide::appendLogText); actionRequired = true; targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); if (darkData) DarkLibrary::Instance()->subtract(darkData, guideView, targetChip->getCaptureFilter(), offsetX, offsetY); else { DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), offsetX, offsetY); } } } break; case GUIDE_STAR_SELECT: { state = GUIDE_STAR_SELECT; emit newStatus(state); if (Options::guideAutoStarEnabled()) { bool autoStarCaptured = internalGuider->selectAutoStar(); if (autoStarCaptured) { appendLogText(i18n("Auto star selected.")); } else { appendLogText(i18n("Failed to select an auto star.")); actionRequired = true; state = GUIDE_CALIBRATION_ERROR; emit newStatus(state); setBusy(false); } } else { appendLogText(i18n("Select a guide star to calibrate.")); actionRequired = true; } } break; default: break; } return actionRequired; } void Guide::processGuideOptions() { if (Options::guiderType() != guiderType) { guiderType = static_cast(Options::guiderType()); setGuiderType(Options::guiderType()); } } void Guide::showFITSViewer() { FITSData *data = guideView->getImageData(); if (data) { QUrl url = QUrl::fromLocalFile(data->filename()); if (fv.isNull()) { if (Options::singleWindowCapturedFITS()) fv = KStars::Instance()->genericFITSViewer(); else { fv = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); KStars::Instance()->addFITSViewer(fv); } fv->addFITS(url); FITSView *currentView = fv->getCurrentView(); if (currentView) currentView->getImageData()->setAutoRemoveTemporaryFITS(false); } else fv->updateFITS(url, 0); fv->show(); } } void Guide::setExternalGuiderBLOBEnabled(bool enable) { // Nothing to do if guider is internal if (guiderType == GUIDE_INTERNAL) return; if(!currentCCD) return; currentCCD->setBLOBEnabled(enable); if(currentCCD->isBLOBEnabled()) { if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) useGuideHead = true; else useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip) { targetChip->setImageView(guideView, FITS_GUIDE); targetChip->setCaptureMode(FITS_GUIDE); } syncCCDInfo(); } } void Guide::ditherDirectly() { double ditherPulse = Options::ditherNoGuidingPulse(); // Randomize pulse length. It is equal to 50% of pulse length + random value up to 50% // e.g. if ditherPulse is 500ms then final pulse is = 250 + rand(0 to 250) int ra_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); int ra_polarity = (rand() % 2 == 0) ? 1 : -1; int de_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); int de_polarity = (rand() % 2 == 0) ? 1 : -1; qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither..."; qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << ra_msec << "ra_polarity:" << ra_polarity << "de_msec:" << de_msec << "de_polarity:" << de_polarity; bool rc = sendPulse(ra_polarity > 0 ? RA_INC_DIR : RA_DEC_DIR, ra_msec, de_polarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR, de_msec); if (rc) { qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful."; QTimer::singleShot( (ra_msec > de_msec ? ra_msec : de_msec) + Options::ditherSettle() * 1000 + 100, [this]() { emit newStatus(GUIDE_DITHERING_SUCCESS); state = GUIDE_IDLE; }); } else { qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed."; emit newStatus(GUIDE_DITHERING_ERROR); state = GUIDE_IDLE; } } void Guide::updateTelescopeType(int index) { if (currentCCD == nullptr) return; focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; Options::setGuideScopeType(index); syncTelescopeInfo(); } void Guide::setDefaultST4(const QString &driver) { Options::setDefaultST4Driver(driver); } void Guide::setDefaultCCD(const QString &ccd) { if (guiderType == GUIDE_INTERNAL) Options::setDefaultGuideCCD(ccd); } void Guide::handleManualDither() { ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip == nullptr) return; Ui::ManualDither ditherDialog; QDialog container(this); ditherDialog.setupUi(&container); if (guiderType != GUIDE_INTERNAL) { ditherDialog.coordinatesR->setEnabled(false); ditherDialog.x->setEnabled(false); ditherDialog.y->setEnabled(false); } int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); ditherDialog.x->setMinimum(minX); ditherDialog.x->setMaximum(maxX); ditherDialog.y->setMinimum(minY); ditherDialog.y->setMaximum(maxY); ditherDialog.x->setValue(starCenter.x()); ditherDialog.y->setValue(starCenter.y()); if (container.exec() == QDialog::Accepted) { if (ditherDialog.magnitudeR->isChecked()) guider->dither(ditherDialog.magnitude->value()); else { dynamic_cast(guider)->ditherXY(ditherDialog.x->value(), ditherDialog.y->value()); } } } bool Guide::connectGuider() { return guider->Connect(); } bool Guide::disconnectGuider() { return guider->Disconnect(); } void Guide::initPlots() { // Drift Graph Color Settings driftGraph->setBackground(QBrush(Qt::black)); driftGraph->xAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftGraph->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftGraph->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftGraph->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftGraph->xAxis->grid()->setZeroLinePen(Qt::NoPen); driftGraph->yAxis->grid()->setZeroLinePen(QPen(Qt::white, 1)); driftGraph->xAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis2->setBasePen(QPen(Qt::white, 1)); driftGraph->xAxis->setTickPen(QPen(Qt::white, 1)); driftGraph->yAxis->setTickPen(QPen(Qt::white, 1)); driftGraph->yAxis2->setTickPen(QPen(Qt::white, 1)); driftGraph->xAxis->setSubTickPen(QPen(Qt::white, 1)); driftGraph->yAxis->setSubTickPen(QPen(Qt::white, 1)); driftGraph->yAxis2->setSubTickPen(QPen(Qt::white, 1)); driftGraph->xAxis->setTickLabelColor(Qt::white); driftGraph->yAxis->setTickLabelColor(Qt::white); driftGraph->yAxis2->setTickLabelColor(Qt::white); driftGraph->xAxis->setLabelColor(Qt::white); driftGraph->yAxis->setLabelColor(Qt::white); driftGraph->yAxis2->setLabelColor(Qt::white); //Horizontal Axis Time Ticker Settings QSharedPointer timeTicker(new QCPAxisTickerTime); timeTicker->setTimeFormat("%m:%s"); driftGraph->xAxis->setTicker(timeTicker); //Vertical Axis Labels Settings driftGraph->yAxis2->setVisible(true); driftGraph->yAxis2->setTickLabels(true); driftGraph->yAxis->setLabelFont(QFont(font().family(), 10)); driftGraph->yAxis2->setLabelFont(QFont(font().family(), 10)); driftGraph->yAxis->setTickLabelFont(QFont(font().family(), 9)); driftGraph->yAxis2->setTickLabelFont(QFont(font().family(), 9)); driftGraph->yAxis->setLabelPadding(1); driftGraph->yAxis2->setLabelPadding(1); driftGraph->yAxis->setLabel(i18n("drift (arcsec)")); driftGraph->yAxis2->setLabel(i18n("pulse (ms)")); setupNSEWLabels(); //Sets the default ranges driftGraph->xAxis->setRange(0, 60, Qt::AlignRight); driftGraph->yAxis->setRange(-3, 3); int scale = 50; //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg correctionSlider->setValue(scale); driftGraph->yAxis2->setRange(-3 * scale, 3 * scale); //This sets up the legend driftGraph->legend->setVisible(true); driftGraph->legend->setFont(QFont("Helvetica", 9)); driftGraph->legend->setTextColor(Qt::white); driftGraph->legend->setBrush(QBrush(Qt::black)); driftGraph->legend->setFillOrder(QCPLegend::foColumnsFirst); driftGraph->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignLeft | Qt::AlignBottom); // RA Curve driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(0)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(0)->setName("RA"); driftGraph->graph(0)->setLineStyle(QCPGraph::lsStepLeft); // DE Curve driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(1)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(1)->setName("DE"); driftGraph->graph(1)->setLineStyle(QCPGraph::lsStepLeft); // RA highlighted Point driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(2)->setLineStyle(QCPGraph::lsNone); driftGraph->graph(2)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10)); // DE highlighted Point driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(3)->setLineStyle(QCPGraph::lsNone); driftGraph->graph(3)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10)); // RA Pulse driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis2); QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); raPulseColor.setAlpha(75); driftGraph->graph(4)->setPen(QPen(raPulseColor)); driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); driftGraph->graph(4)->setName("RA Pulse"); driftGraph->graph(4)->setLineStyle(QCPGraph::lsStepLeft); // DEC Pulse driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis2); QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); dePulseColor.setAlpha(75); driftGraph->graph(5)->setPen(QPen(dePulseColor)); driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); driftGraph->graph(5)->setName("DEC Pulse"); driftGraph->graph(5)->setLineStyle(QCPGraph::lsStepLeft); //This will prevent the highlighted points and Pulses from showing up in the legend. driftGraph->legend->removeItem(5); driftGraph->legend->removeItem(4); driftGraph->legend->removeItem(3); driftGraph->legend->removeItem(2); //Dragging and zooming settings // make bottom axis transfer its range to the top axis if the graph gets zoomed: connect(driftGraph->xAxis, static_cast(&QCPAxis::rangeChanged), driftGraph->xAxis2, static_cast(&QCPAxis::setRange)); // update the second vertical axis properly if the graph gets zoomed. connect(driftGraph->yAxis, static_cast(&QCPAxis::rangeChanged), this, &Ekos::Guide::setCorrectionGraphScale); driftGraph->setInteractions(QCP::iRangeZoom); driftGraph->setInteraction(QCP::iRangeDrag, true); connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); //drift plot double accuracyRadius = 2; driftPlot->setBackground(QBrush(Qt::black)); driftPlot->setSelectionTolerance(10); driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); driftPlot->xAxis->setTickPen(QPen(Qt::white, 1)); driftPlot->yAxis->setTickPen(QPen(Qt::white, 1)); driftPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); driftPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); driftPlot->xAxis->setTickLabelColor(Qt::white); driftPlot->yAxis->setTickLabelColor(Qt::white); driftPlot->xAxis->setLabelColor(Qt::white); driftPlot->yAxis->setLabelColor(Qt::white); driftPlot->xAxis->setLabelFont(QFont(font().family(), 10)); driftPlot->yAxis->setLabelFont(QFont(font().family(), 10)); driftPlot->xAxis->setTickLabelFont(QFont(font().family(), 9)); driftPlot->yAxis->setTickLabelFont(QFont(font().family(), 9)); driftPlot->xAxis->setLabelPadding(2); driftPlot->yAxis->setLabelPadding(2); driftPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray)); driftPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray)); driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->setInteractions(QCP::iRangeZoom); driftPlot->setInteraction(QCP::iRangeDrag, true); driftPlot->addGraph(); driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); driftPlot->addGraph(); driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); //This sets the values of all the Graph Options that are stored. accuracyRadiusSpin->setValue(Options::guiderAccuracyThreshold()); showRAPlotCheck->setChecked(Options::rADisplayedOnGuideGraph()); showDECPlotCheck->setChecked(Options::dEDisplayedOnGuideGraph()); showRACorrectionsCheck->setChecked(Options::rACorrDisplayedOnGuideGraph()); showDECorrectionsCheck->setChecked(Options::dECorrDisplayedOnGuideGraph()); //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(); driftPlot->resize(190, 190); driftPlot->replot(); buildTarget(); } void Guide::initView() { guideView = new FITSView(guideWidget, FITS_GUIDE); guideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); guideView->setBaseSize(guideWidget->size()); guideView->createFloatingToolBar(); QVBoxLayout *vlayout = new QVBoxLayout(); vlayout->addWidget(guideView); guideWidget->setLayout(vlayout); connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar); } void Guide::initConnections() { // Exposure Timeout captureTimeout.setSingleShot(true); connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout); // Guiding Box Size connect(boxSizeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTrackingBoxSize); // Guider CCD Selection connect(guiderCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::setDefaultCCD); connect(guiderCombo, static_cast(&QComboBox::activated), this, [&](int index) { if (guiderType == GUIDE_INTERNAL) { starCenter = QVector3D(); checkCCD(index); } } ); FOVScopeCombo->setCurrentIndex(Options::guideScopeType()); connect(FOVScopeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTelescopeType); // Dark Frame Check connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); // Subframe check if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); // ST4 Selection connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString & text) { setDefaultST4(text); setST4(text); }); // Binning Combo Selection connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateCCDBin); // RA/DEC Enable directions connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); // N/W and W/E direction enable connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(westControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(eastControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); // Auto star check connect(autoStarCheck, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); // Declination Swap connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); // PID Control - Proportional Gain connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // PID Control - Integral Gain connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // PID Control - Derivative Gain connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Max Pulse Duration (ms) connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Min Pulse Duration (ms) connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Capture connect(captureB, &QPushButton::clicked, this, [this]() { state = GUIDE_CAPTURE; emit newStatus(state); if(guiderType == GUIDE_PHD2) { configurePHD2Camera(); if(phd2Guider->isCurrentCameraNotInEkos()) appendLogText(i18n("The PHD2 camera is not available to Ekos, so you cannot see the captured images. But you will still see the Guide Star Image when you guide.")); else if(Options::guideSubframeEnabled()) { appendLogText(i18n("To receive PHD2 images other than the Guide Star Image, SubFrame must be unchecked. Unchecking it now to enable your image captures. You can re-enable it before Guiding")); subFrameCheck->setChecked(false); } phd2Guider->captureSingleFrame(); } else capture(); }); connect(loopB, &QPushButton::clicked, this, [this]() { state = GUIDE_LOOPING; emit newStatus(state); if(guiderType == GUIDE_PHD2) { configurePHD2Camera(); if(phd2Guider->isCurrentCameraNotInEkos()) appendLogText(i18n("The PHD2 camera is not available to Ekos, so you cannot see the captured images. But you will still see the Guide Star Image when you guide.")); else if(Options::guideSubframeEnabled()) { appendLogText(i18n("To receive PHD2 images other than the Guide Star Image, SubFrame must be unchecked. Unchecking it now to enable your image captures. You can re-enable it before Guiding")); subFrameCheck->setChecked(false); } phd2Guider->loop(); stopB->setEnabled(true); } else capture(); }); // Stop connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); // Clear Calibrate //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); // Guide connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); // Connect External Guide connect(externalConnectB, &QPushButton::clicked, this, [&]() { //setExternalGuiderBLOBEnabled(false); guider->Connect(); }); connect(externalDisconnectB, &QPushButton::clicked, this, [&]() { //setExternalGuiderBLOBEnabled(true); guider->Disconnect(); }); // Pulse Timer pulseTimer.setSingleShot(true); connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); //This connects all the buttons and slider below the guide plots. connect(accuracyRadiusSpin, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::buildTarget); connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory); connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint); connect(showRAPlotCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleShowRAPlot); connect(showDECPlotCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleShowDEPlot); connect(showRACorrectionsCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleRACorrectionsPlot); connect(showDECorrectionsCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleDECorrectionsPlot); connect(correctionSlider, &QSlider::sliderMoved, this, &Ekos::Guide::setCorrectionGraphScale); connect(showGuideRateToolTipB, &QPushButton::clicked, [this]() { QToolTip::showText(showGuideRateToolTipB->mapToGlobal(QPoint(10, 10)), showGuideRateToolTipB->toolTip(), showGuideRateToolTipB); }); connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither); // Guiding Rate - Advisory only onInfoRateChanged(spinBox_GuideRate->value()); connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); } void Guide::removeDevice(ISD::GDInterface *device) { device->disconnect(this); if (currentTelescope && !strcmp(currentTelescope->getDeviceName(), device->getDeviceName())) { currentTelescope = nullptr; } else if (CCDs.contains(static_cast(device))) { CCDs.removeAll(static_cast(device)); guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName())); guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName() + QString(" Guider"))); if (CCDs.empty()) currentCCD = nullptr; checkCCD(); } auto st4 = std::find_if(ST4List.begin(), ST4List.end(), [device](ISD::ST4 * st) { return !strcmp(st->getDeviceName(), device->getDeviceName()); }); if (st4 != ST4List.end()) { ST4List.removeOne(*st4); if (AODriver && !strcmp(device->getDeviceName(), AODriver->getDeviceName())) AODriver = nullptr; ST4Combo->removeItem(ST4Combo->findText(device->getDeviceName())); if (ST4List.empty()) { ST4Driver = GuideDriver = nullptr; } else { setST4(ST4Combo->currentText()); } } } } diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h index 94fd93661..2db4d655c 100644 --- a/kstars/ekos/guide/guide.h +++ b/kstars/ekos/guide/guide.h @@ -1,641 +1,642 @@ /* Ekos guide tool Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "ui_guide.h" #include "ekos/ekos.h" #include "indi/indiccd.h" #include "indi/inditelescope.h" #include #include #include class QProgressIndicator; class QTabWidget; class FITSView; class FITSViewer; class ScrollGraph; namespace Ekos { class GuideInterface; class OpsCalibration; class OpsGuide; class InternalGuider; class PHD2; class LinGuider; /** * @class Guide * @short Performs calibration and autoguiding using an ST4 port or directly via the INDI driver. Can be used with the following external guiding applications: * PHD2 * LinGuider * * @author Jasem Mutlaq * @version 1.4 */ class Guide : public QWidget, public Ui::Guide { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Guide") Q_PROPERTY(Ekos::GuideState status READ status NOTIFY newStatus) Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) Q_PROPERTY(QString camera READ camera WRITE setCamera) Q_PROPERTY(QString st4 READ st4 WRITE setST4) Q_PROPERTY(double exposure READ exposure WRITE setExposure) Q_PROPERTY(QList axisDelta READ axisDelta NOTIFY newAxisDelta) Q_PROPERTY(QList axisSigma READ axisSigma NOTIFY newAxisSigma) public: Guide(); ~Guide(); enum GuiderStage { CALIBRATION_STAGE, GUIDE_STAGE }; enum GuiderType { GUIDE_INTERNAL, GUIDE_PHD2, GUIDE_LINGUIDER }; /** @defgroup GuideDBusInterface Ekos DBus Interface - Capture Module * Ekos::Guide interface provides advanced scripting capabilities to calibrate and guide a mount via a CCD camera. */ /*@{*/ /** DBUS interface function. * select the CCD device from the available CCD drivers. * @param device The CCD device name * @return Returns true if CCD device is found and set, false otherwise. */ Q_SCRIPTABLE bool setCamera(const QString &device); Q_SCRIPTABLE QString camera(); /** DBUS interface function. * select the ST4 device from the available ST4 drivers. * @param device The ST4 device name * @return Returns true if ST4 device is found and set, false otherwise. */ Q_SCRIPTABLE bool setST4(const QString &device); Q_SCRIPTABLE QString st4(); /** DBUS interface function. * @return Returns List of registered ST4 devices. */ Q_SCRIPTABLE QStringList getST4Devices(); /** DBUS interface function. * @brief connectGuider Establish connection to guider application. For internal guider, this always returns true. * @return True if successfully connected, false otherwise. */ Q_SCRIPTABLE bool connectGuider(); /** DBUS interface function. * @brief disconnectGuider Disconnect from guider application. For internal guider, this always returns true. * @return True if successfully disconnected, false otherwise. */ Q_SCRIPTABLE bool disconnectGuider(); /** * @brief getStatus Return guide module status * @return state of guide module from Ekos::GuideState */ Q_SCRIPTABLE Ekos::GuideState status() { return state; } /** DBUS interface function. * Set CCD exposure value * @param value exposure value in seconds. */ Q_SCRIPTABLE Q_NOREPLY void setExposure(double value); double exposure() { return exposureIN->value(); } /** DBUS interface function. * Set image filter to apply to the image after capture. * @param value Image filter (Auto Stretch, High Contrast, Equalize, High Pass) */ Q_SCRIPTABLE Q_NOREPLY void setImageFilter(const QString &value); /** DBUS interface function. * Set calibration Use Two Axis option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, calibration will be performed in both RA and DEC axis. Otherwise, only RA axis will be calibrated. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationTwoAxis(bool enable); /** DBUS interface function. * Set auto star calibration option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will attempt to automatically select the best guide star and proceed with the calibration procedure. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoStar(bool enable); /** DBUS interface function. * In case of automatic star selection, calculate the appropriate square size given the selected star width. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will attempt to automatically select the best square size for calibration and guiding phases. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoSquareSize(bool enable); /** DBUS interface function. * Set calibration dark frame option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, a dark frame will be captured to subtract from the light frame. */ Q_SCRIPTABLE Q_NOREPLY void setDarkFrameEnabled(bool enable); /** DBUS interface function. * Set calibration parameters. * @param pulseDuration Pulse duration in milliseconds to use in the calibration steps. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationPulseDuration(int pulseDuration); /** DBUS interface function. * Set guiding box size. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param index box size index (0 to 4) for box size from 8 to 128 pixels. The box size should be suitable for the size of the guide star selected. The boxSize is also used to select the subframe size around the guide star. Default is 16 pixels */ Q_SCRIPTABLE Q_NOREPLY void setGuideBoxSizeIndex(int index); /** DBUS interface function. * Set guiding algorithm. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param index Select the algorithm used to calculate the centroid of the guide star (0 --> Smart, 1 --> Fast, 2 --> Auto, 3 --> No thresh). */ Q_SCRIPTABLE Q_NOREPLY void setGuideAlgorithmIndex(int index); /** DBUS interface function. * Set rapid guiding option. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, it will activate RapidGuide in the CCD driver. When Rapid Guide is used, no frames are sent to Ekos for analysis and the centeroid calculations are done in the CCD driver. */ //Q_SCRIPTABLE Q_NOREPLY void setGuideRapidEnabled(bool enable); /** DBUS interface function. * Enable or disables dithering. Set dither range * @param enable if true, dithering is enabled and is performed after each exposure is complete. Otherwise, dithering is disabled. * @param value dithering range in pixels. Ekos will move the guide star in a random direction for the specified dithering value in pixels. */ Q_SCRIPTABLE Q_NOREPLY void setDitherSettings(bool enable, double value); /** @}*/ void addCamera(ISD::GDInterface *newCCD); void configurePHD2Camera(); void setTelescope(ISD::GDInterface *newTelescope); void addST4(ISD::ST4 *setST4); void setAO(ISD::ST4 *newAO); void removeDevice(ISD::GDInterface *device); bool isDithering(); void addGuideHead(ISD::GDInterface *newCCD); void syncTelescopeInfo(); void syncCCDInfo(); /** * @brief clearLog As the name suggests */ void clearLog(); QStringList logText() { return m_LogText; } /** * @return Return current log text of guide module */ QString getLogText() { return m_LogText.join("\n"); } /** * @brief getStarPosition Return star center as selected by the user or auto-detected by KStars * @return QVector3D of starCenter. The 3rd parameter is used to store current bin settings and in unrelated to the star position. */ QVector3D getStarPosition() { return starCenter; } // Tracking Box int getTrackingBoxSize() { return boxSizeCombo->currentText().toInt(); } //void startRapidGuide(); //void stopRapidGuide(); GuideInterface *getGuider() { return guider; } public slots: /** DBUS interface function. * Start the autoguiding operation. * @return Returns true if guiding started successfully, false otherwise. */ Q_SCRIPTABLE bool guide(); /** DBUS interface function. * Stop any active calibration, guiding, or dithering operation * @return Returns true if operation is stopped successfully, false otherwise. */ Q_SCRIPTABLE bool abort(); /** DBUS interface function. * Start the calibration operation. Note that this will not start guiding automatically. * @return Returns true if calibration started successfully, false otherwise. */ Q_SCRIPTABLE bool calibrate(); /** DBUS interface function. * Clear calibration data. Next time any guide operation is performed, a calibration is first started. */ Q_SCRIPTABLE Q_NOREPLY void clearCalibration(); /** DBUS interface function. * @brief dither Starts dithering process in a random direction restricted by the number of pixels specified in dither options * @return True if dither started successfully, false otherwise. */ Q_SCRIPTABLE bool dither(); /** DBUS interface function. * @brief suspend Suspend autoguiding * @return True if successful, false otherwise. */ Q_SCRIPTABLE bool suspend(); /** DBUS interface function. * @brief resume Resume autoguiding * @return True if successful, false otherwise. */ Q_SCRIPTABLE bool resume(); /** DBUS interface function. * Capture a guide frame * @return Returns true if capture command is sent successfully to INDI server. */ Q_SCRIPTABLE bool capture(); /** DBUS interface function. * Set guiding options. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, it will select a subframe around the guide star depending on the boxSize size. */ Q_SCRIPTABLE Q_NOREPLY void setSubFrameEnabled(bool enable); /** DBUS interface function. * Selects which guiding process to utilize for calibration & guiding. * @param type Type of guider process to use. 0 for internal guider, 1 for external PHD2, 2 for external lin_guider. Pass -1 to select default guider in options. * @return True if guiding is switched to the new requested type. False otherwise. */ Q_SCRIPTABLE bool setGuiderType(int type); /** DBUS interface function. * @brief axisDelta returns the last immediate axis delta deviation in arcseconds. This is the deviation of locked star position when guiding started. * @return List of doubles. First member is RA deviation. Second member is DE deviation. */ Q_SCRIPTABLE QList axisDelta(); /** DBUS interface function. * @brief axisSigma return axis sigma deviation in arcseconds RMS. This is the RMS deviation of locked star position when guiding started. * @return List of doubles. First member is RA deviation. Second member is DE deviation. */ Q_SCRIPTABLE QList axisSigma(); /** * @brief checkCCD Check all CCD parameters and ensure all variables are updated to reflect the selected CCD * @param ccdNum CCD index number in the CCD selection combo box */ void checkCCD(int ccdNum = -1); /** * @brief checkExposureValue This function is called by the INDI framework whenever there is a new exposure value. We use it to know if there is a problem with the exposure * @param targetChip Chip for which the exposure is undergoing * @param exposure numbers of seconds left in the exposure * @param expState State of the exposure property */ void checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState); /** * @brief newFITS is called by the INDI framework whenever there is a new BLOB arriving */ void newFITS(IBLOB *); /** * @brief setST4 Sets a new ST4 device from the combobox index * @param index Index of selected ST4 in the combobox */ void setST4(int index); /* * @brief processRapidStarData is called by INDI framework when we receive new Rapid Guide data * @param targetChip target Chip for which rapid guide is enabled * @param dx Deviation in X * @param dy Deviation in Y * @param fit fitting score */ //void processRapidStarData(ISD::CCDChip *targetChip, double dx, double dy, double fit); /** * @brief Set telescope and guide scope info. All measurements is in millimeters. * @param primaryFocalLength Primary Telescope Focal Length. Set to 0 to skip setting this value. * @param primaryAperture Primary Telescope Aperture. Set to 0 to skip setting this value. * @param guideFocalLength Guide Telescope Focal Length. Set to 0 to skip setting this value. * @param guideAperture Guide Telescope Aperture. Set to 0 to skip setting this value. */ void setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture); // This Function will allow PHD2 to update the exposure values to the recommended ones. QString setRecommendedExposureValues(QList values); // Append Log entry void appendLogText(const QString &); // Update Guide module status void setStatus(Ekos::GuideState newState); // Update Capture Module status void setCaptureStatus(Ekos::CaptureState newState); // Update Mount module status void setMountStatus(ISD::Telescope::Status newState); // Update Pier Side void setPierSide(ISD::Telescope::PierSide newSide); // Star Position void setStarPosition(const QVector3D &newCenter, bool updateNow); // Capture void setCaptureComplete(); // Send pulse to ST4 driver bool sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs); bool sendPulse(GuideDirection dir, int msecs); /** * @brief setDECSwap Change ST4 declination pulse direction. +DEC pulses increase DEC if swap is OFF. When on +DEC pulses result in decreasing DEC. * @param enable True to enable DEC swap. Off to disable it. */ void setDECSwap(bool enable); void refreshColorScheme(); void setupNSEWLabels(); //plot slots void handleVerticalPlotSizeChange(); void handleHorizontalPlotSizeChange(); void clearGuideGraphs(); void slotAutoScaleGraphs(); void buildTarget(); void guideHistory(); void setLatestGuidePoint(bool isChecked); void toggleShowRAPlot(bool isChecked); void toggleShowDEPlot(bool isChecked); void toggleRACorrectionsPlot(bool isChecked); void toggleDECorrectionsPlot(bool isChecked); void exportGuideData(); void setCorrectionGraphScale(); void updateCorrectionsScaleVisibility(); void updateDirectionsFromPHD2(QString mode); + void guideAfterMeridianFlip(); protected slots: void updateTelescopeType(int index); void updateCCDBin(int index); /** * @brief processCCDNumber Process number properties arriving from CCD. Currently, binning changes are processed. * @param nvp pointer to number property. */ void processCCDNumber(INumberVectorProperty *nvp); /** * @brief setTrackingStar Gets called when the user select a star in the guide frame * @param x X coordinate of star * @param y Y coordinate of star */ void setTrackingStar(int x, int y); void saveDefaultGuideExposure(); void updateTrackingBoxSize(int currentIndex); // Display guide information when hovering over the drift graph void driftMouseOverLine(QMouseEvent *event); // Reset graph if right clicked void driftMouseClicked(QMouseEvent *event); //void onXscaleChanged( int i ); //void onYscaleChanged( int i ); void onThresholdChanged(int i); void onInfoRateChanged(double val); void onEnableDirRA(bool enable); void onEnableDirDEC(bool enable); void syncSettings(); //void onRapidGuideChanged(bool enable); void setAxisDelta(double ra, double de); void setAxisSigma(double ra, double de); void setAxisPulse(double ra, double de); void processGuideOptions(); void onControlDirectionChanged(bool enable); void updatePHD2Directions(); void showFITSViewer(); void processCaptureTimeout(); void ditherDirectly(); signals: void newLog(const QString &text); void newStatus(Ekos::GuideState status); void newStarPixmap(QPixmap &); void newProfilePixmap(QPixmap &); // Immediate deviations in arcsecs void newAxisDelta(double ra, double de); // Sigma deviations in arcsecs RMS void newAxisSigma(double ra, double de); void guideChipUpdated(ISD::CCDChip *); private slots: void setDefaultST4(const QString &driver); void setDefaultCCD(const QString &ccd); private: void resizeEvent(QResizeEvent *event) override; /** * @brief updateGuideParams Update the guider and frame parameters due to any changes in the mount and/or ccd frame */ void updateGuideParams(); /** * @brief syncTrackingBoxPosition Sync the tracking box to the current selected star center */ void syncTrackingBoxPosition(); /** * @brief loadSettings Loads and applies all settings from KStars options */ void loadSettings(); /** * @brief saveSettings Saves all current settings into KStars options */ void saveSettings(); /** * @brief setBusy Indicate busy status within the module visually * @param enable True if module is busy, false otherwise */ void setBusy(bool enable); /** * @brief setBLOBEnabled Enable or disable BLOB reception from current CCD if using external guider * @param enable True to enable BLOB reception, false to disable BLOB reception * @param name CCD to enable to disable. If empty (default), then action is applied to all CCDs. */ void setExternalGuiderBLOBEnabled(bool enable); void handleManualDither(); // Operation stack void buildOperationStack(GuideState operation); bool executeOperationStack(); bool executeOneOperation(GuideState operation); // Init Functions void initPlots(); void initView(); void initConnections(); bool captureOneFrame(); // Operation Stack QStack operationStack; // Devices ISD::CCD *currentCCD { nullptr }; QString lastPHD2CameraName; //This is for the configure PHD2 camera method. ISD::Telescope *currentTelescope { nullptr }; ISD::ST4 *ST4Driver { nullptr }; ISD::ST4 *AODriver { nullptr }; ISD::ST4 *GuideDriver { nullptr }; // Device Containers QList ST4List; QList CCDs; // Guider process GuideInterface *guider { nullptr }; GuiderType guiderType; // Star QVector3D starCenter; // Guide Params double ccdPixelSizeX { -1 }; double ccdPixelSizeY { -1 }; double aperture { -1 }; double focal_length { -1 }; double guideDeviationRA { 0 }; double guideDeviationDEC { 0 }; double pixScaleX { -1 }; double pixScaleY { -1 }; // Rapid Guide //bool rapidGuideReticleSet; // State GuideState state { GUIDE_IDLE }; // Guide timer QTime guideTimer; // Capture timeout timer QTimer captureTimeout; uint8_t captureTimeoutCounter { 0 }; // Pulse Timer QTimer pulseTimer; // Log QStringList m_LogText; // Misc bool useGuideHead { false }; // Progress Activity Indicator QProgressIndicator *pi { nullptr }; // Options OpsCalibration *opsCalibration { nullptr }; OpsGuide *opsGuide { nullptr }; // Guide Frame FITSView *guideView { nullptr }; // Calibration done already? bool calibrationComplete { false }; // Was the modified frame subFramed? bool subFramed { false }; // CCD Chip frame settings QMap frameSettings; // Profile Pixmap QPixmap profilePixmap; // Flag to start auto calibration followed immediately by guiding //bool autoCalibrateGuide { false }; // Pointers of guider processes QPointer internalGuider; QPointer phd2Guider; QPointer linGuider; QPointer fv; double primaryFL = -1, primaryAperture = -1, guideFL = -1, guideAperture = -1; QCPCurve *centralTarget { nullptr }; QCPCurve *yellowTarget { nullptr }; QCPCurve *redTarget { nullptr }; QCPCurve *concentricRings { nullptr }; bool graphOnLatestPt = true; QUrl guideURLPath; //This is for enforcing the PHD2 Star lock when Guide is pressed, //autostar is not selected, and the user has chosen a star. //This connection storage is so that the connection can be disconnected after enforcement QMetaObject::Connection guideConnect; }; } diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp index 976e5c027..83822bfc1 100644 --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -1,3367 +1,3362 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "manager.h" #include "ekosadaptor.h" #include "kstars.h" #include "kstarsdata.h" #include "opsekos.h" #include "Options.h" #include "profileeditor.h" #include "profilewizard.h" #include "skymap.h" #include "auxiliary/darklibrary.h" #include "auxiliary/QProgressIndicator.h" #include "auxiliary/ksmessagebox.h" #include "capture/sequencejob.h" #include "fitsviewer/fitstab.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsdata.h" #include "indi/clientmanager.h" #include "indi/driverinfo.h" #include "indi/drivermanager.h" #include "indi/guimanager.h" #include "indi/indielement.h" #include "indi/indilistener.h" #include "indi/indiproperty.h" #include "indi/indiwebmanager.h" #include "ekoslive/ekosliveclient.h" #include "ekoslive/message.h" #include "ekoslive/media.h" #include #include #include #include #include #include #include #define MAX_REMOTE_INDI_TIMEOUT 15000 #define MAX_LOCAL_INDI_TIMEOUT 5000 namespace Ekos { Manager::Manager(QWidget * parent) : QDialog(parent) { #ifdef Q_OS_OSX if (Options::independentWindowEkos()) setWindowFlags(Qt::Window); else { setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(changeAlwaysOnTop(Qt::ApplicationState))); } #else // if (Options::independentWindowEkos()) // setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); #endif setupUi(this); qRegisterMetaType("Ekos::CommunicationStatus"); qDBusRegisterMetaType(); new EkosAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos", this); setWindowIcon(QIcon::fromTheme("kstars_ekos")); profileModel.reset(new QStandardItemModel(0, 4)); profileModel->setHorizontalHeaderLabels(QStringList() << "id" << "name" << "host" << "port"); captureProgress->setValue(0); sequenceProgress->setValue(0); sequenceProgress->setDecimals(0); sequenceProgress->setFormat("%v"); imageProgress->setValue(0); imageProgress->setDecimals(1); imageProgress->setFormat("%v"); imageProgress->setBarStyle(QRoundProgressBar::StyleLine); countdownTimer.setInterval(1000); connect(&countdownTimer, &QTimer::timeout, this, &Ekos::Manager::updateCaptureCountDown); toolsWidget->setIconSize(QSize(48, 48)); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); // Enable scheduler Tab toolsWidget->setTabEnabled(1, false); // Start/Stop INDI Server connect(processINDIB, &QPushButton::clicked, this, &Ekos::Manager::processINDI); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); // Connect/Disconnect INDI devices connect(connectB, &QPushButton::clicked, this, &Ekos::Manager::connectDevices); connect(disconnectB, &QPushButton::clicked, this, &Ekos::Manager::disconnectDevices); ekosLiveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); ekosLiveClient.reset(new EkosLive::Client(this)); connect(ekosLiveClient.get(), &EkosLive::Client::connected, [this]() { emit ekosLiveStatusChanged(true); }); connect(ekosLiveClient.get(), &EkosLive::Client::disconnected, [this]() { emit ekosLiveStatusChanged(false); }); // INDI Control Panel //connect(controlPanelB, &QPushButton::clicked, GUIManager::Instance(), SLOT(show())); connect(ekosLiveB, &QPushButton::clicked, [&]() { ekosLiveClient.get()->show(); ekosLiveClient.get()->raise(); }); connect(this, &Manager::ekosStatusChanged, ekosLiveClient.get()->message(), &EkosLive::Message::setEkosStatingStatus); connect(ekosLiveClient.get()->message(), &EkosLive::Message::connected, [&]() { ekosLiveB->setIcon(QIcon(":/icons/cloud-online.svg")); }); connect(ekosLiveClient.get()->message(), &EkosLive::Message::disconnected, [&]() { ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud")); }); connect(ekosLiveClient.get()->media(), &EkosLive::Media::newBoundingRect, ekosLiveClient.get()->message(), &EkosLive::Message::setBoundingRect); connect(ekosLiveClient.get()->message(), &EkosLive::Message::resetPolarView, ekosLiveClient.get()->media(), &EkosLive::Media::resetPolarView); connect(ekosLiveClient.get()->message(), &EkosLive::Message::previewJPEGGenerated, ekosLiveClient.get()->media(), &EkosLive::Media::sendPreviewJPEG); connect(KSMessageBox::Instance(), &KSMessageBox::newMessage, ekosLiveClient.get()->message(), &EkosLive::Message::sendDialog); // Serial Port Assistant connect(serialPortAssistantB, &QPushButton::clicked, [&]() { serialPortAssistant->show(); serialPortAssistant->raise(); }); connect(this, &Ekos::Manager::ekosStatusChanged, [&](Ekos::CommunicationStatus status) { indiControlPanelB->setEnabled(status == Ekos::Success); }); connect(indiControlPanelB, &QPushButton::clicked, [&]() { KStars::Instance()->actionCollection()->action("show_control_panel")->trigger(); }); connect(optionsB, &QPushButton::clicked, [&]() { KStars::Instance()->actionCollection()->action("configure")->trigger(); }); // Save as above, but it appears in all modules connect(ekosOptionsB, &QPushButton::clicked, this, &Ekos::Manager::showEkosOptions); // Clear Ekos Log connect(clearB, &QPushButton::clicked, this, &Ekos::Manager::clearLog); // Logs KConfigDialog * dialog = new KConfigDialog(this, "logssettings", Options::self()); opsLogs = new Ekos::OpsLogs(); KPageWidgetItem * page = dialog->addPage(opsLogs, i18n("Logging")); page->setIcon(QIcon::fromTheme("configure")); connect(logsB, &QPushButton::clicked, dialog, &KConfigDialog::show); connect(dialog->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); connect(dialog->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); // Summary // previewPixmap = new QPixmap(QPixmap(":/images/noimage.png")); // Profiles connect(addProfileB, &QPushButton::clicked, this, &Ekos::Manager::addProfile); connect(editProfileB, &QPushButton::clicked, this, &Ekos::Manager::editProfile); connect(deleteProfileB, &QPushButton::clicked, this, &Ekos::Manager::deleteProfile); connect(profileCombo, static_cast(&QComboBox::currentTextChanged), [ = ](const QString & text) { Options::setProfile(text); if (text == "Simulators") { editProfileB->setEnabled(false); deleteProfileB->setEnabled(false); } else { editProfileB->setEnabled(true); deleteProfileB->setEnabled(true); } }); // Ekos Wizard connect(wizardProfileB, &QPushButton::clicked, this, &Ekos::Manager::wizardProfile); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Set Profile icons addProfileB->setIcon(QIcon::fromTheme("list-add")); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setIcon(QIcon::fromTheme("document-edit")); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setIcon(QIcon::fromTheme("list-remove")); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); wizardProfileB->setIcon(QIcon::fromTheme("tools-wizard")); wizardProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); customDriversB->setIcon(QIcon::fromTheme("roll")); customDriversB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(customDriversB, &QPushButton::clicked, DriverManager::Instance(), &DriverManager::showCustomDrivers); // Load all drivers loadDrivers(); // Load add driver profiles loadProfiles(); // INDI Control Panel and Ekos Options optionsB->setIcon(QIcon::fromTheme("configure", QIcon(":/icons/ekos_setup.png"))); optionsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Setup Tab toolsWidget->tabBar()->setTabIcon(0, QIcon(":/icons/ekos_setup.png")); toolsWidget->tabBar()->setTabToolTip(0, i18n("Setup")); // Initialize Ekos Scheduler Module schedulerProcess.reset(new Ekos::Scheduler()); toolsWidget->addTab(schedulerProcess.get(), QIcon(":/icons/ekos_scheduler.png"), ""); toolsWidget->tabBar()->setTabToolTip(1, i18n("Scheduler")); connect(schedulerProcess.get(), &Scheduler::newLog, this, &Ekos::Manager::updateLog); //connect(schedulerProcess.get(), SIGNAL(newTarget(QString)), mountTarget, SLOT(setText(QString))); connect(schedulerProcess.get(), &Ekos::Scheduler::newTarget, [&](const QString & target) { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); // Temporary fix. Not sure how to resize Ekos Dialog to fit contents of the various tabs in the QScrollArea which are added // dynamically. I used setMinimumSize() but it doesn't appear to make any difference. // Also set Layout policy to SetMinAndMaxSize as well. Any idea how to fix this? // FIXME //resize(1000,750); summaryPreview.reset(new FITSView(previewWidget, FITS_NORMAL)); previewWidget->setContentsMargins(0, 0, 0, 0); summaryPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); summaryPreview->setBaseSize(previewWidget->size()); summaryPreview->createFloatingToolBar(); summaryPreview->setCursorMode(FITSView::dragCursor); QVBoxLayout * vlayout = new QVBoxLayout(); vlayout->addWidget(summaryPreview.get()); previewWidget->setLayout(vlayout); // JM 2019-01-19: Why cloud images depend on summary preview? // connect(summaryPreview.get(), &FITSView::loaded, [&]() // { // // UUID binds the cloud & preview frames by a common key // QString uuid = QUuid::createUuid().toString(); // uuid = uuid.remove(QRegularExpression("[-{}]")); // //ekosLiveClient.get()->media()->sendPreviewImage(summaryPreview.get(), uuid); // ekosLiveClient.get()->cloud()->sendPreviewImage(summaryPreview.get(), uuid); // }); if (Options::ekosLeftIcons()) { toolsWidget->setTabPosition(QTabWidget::West); QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(0); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(0, icon); icon = toolsWidget->tabIcon(1); pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(1, icon); } //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); resize(Options::ekosWindowWidth(), Options::ekosWindowHeight()); } void Manager::changeAlwaysOnTop(Qt::ApplicationState state) { if (isVisible()) { if (state == Qt::ApplicationActive) setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); else setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); } } Manager::~Manager() { toolsWidget->disconnect(this); //delete previewPixmap; } void Manager::closeEvent(QCloseEvent * event) { // QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); // a->setChecked(false); // 2019-02-14 JM: Close event, for some reason, make all the children disappear // when the widget is shown again. Applying a workaround here event->ignore(); hide(); } void Manager::hideEvent(QHideEvent * /*event*/) { Options::setEkosWindowWidth(width()); Options::setEkosWindowHeight(height()); QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); } void Manager::showEvent(QShowEvent * /*event*/) { QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(true); // Just show the profile wizard ONCE per session if (profileWizardLaunched == false && profiles.count() == 1) { profileWizardLaunched = true; wizardProfile(); } } void Manager::resizeEvent(QResizeEvent *) { //previewImage->setPixmap(previewPixmap->scaled(previewImage->width(), previewImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (focusStarPixmap.get() != nullptr) focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (focusProfilePixmap) //focusProfileImage->setPixmap(focusProfilePixmap->scaled(focusProfileImage->width(), focusProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (guideStarPixmap.get() != nullptr) guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (guideProfilePixmap) //guideProfileImage->setPixmap(guideProfilePixmap->scaled(guideProfileImage->width(), guideProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::loadProfiles() { profiles.clear(); KStarsData::Instance()->userdb()->GetAllProfiles(profiles); profileModel->clear(); for (auto &pi : profiles) { QList info; info << new QStandardItem(pi->id) << new QStandardItem(pi->name) << new QStandardItem(pi->host) << new QStandardItem(pi->port); profileModel->appendRow(info); } profileModel->sort(0); profileCombo->blockSignals(true); profileCombo->setModel(profileModel.get()); profileCombo->setModelColumn(1); profileCombo->blockSignals(false); // Load last used profile from options int index = profileCombo->findText(Options::profile()); // If not found, set it to first item if (index == -1) index = 0; profileCombo->setCurrentIndex(index); } void Manager::loadDrivers() { foreach (DriverInfo * dv, DriverManager::Instance()->getDrivers()) { if (dv->getDriverSource() != HOST_SOURCE) driversList[dv->getLabel()] = dv; } } void Manager::reset() { qCDebug(KSTARS_EKOS) << "Resetting Ekos Manager..."; // Filter Manager filterManager.reset(new Ekos::FilterManager()); nDevices = 0; useGuideHead = false; useST4 = false; removeTabs(); genericDevices.clear(); managedDevices.clear(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); domeProcess.reset(); alignProcess.reset(); mountProcess.reset(); weatherProcess.reset(); observatoryProcess.reset(); dustCapProcess.reset(); DarkLibrary::Instance()->reset(); Ekos::CommunicationStatus previousStatus = m_ekosStatus; m_ekosStatus = Ekos::Idle; if (previousStatus != m_ekosStatus) emit ekosStatusChanged(m_ekosStatus); previousStatus = m_indiStatus; m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); processINDIB->setEnabled(true); mountGroup->setEnabled(false); focusGroup->setEnabled(false); captureGroup->setEnabled(false); guideGroup->setEnabled(false); sequenceLabel->setText(i18n("Sequence")); sequenceProgress->setValue(0); captureProgress->setValue(0); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); imageRemainingTime->setText("--:--:--"); mountStatus->setText(i18n("Idle")); mountStatus->setStyleSheet(QString()); captureStatus->setText(i18n("Idle")); focusStatus->setText(i18n("Idle")); guideStatus->setText(i18n("Idle")); if (capturePI) capturePI->stopAnimation(); if (mountPI) mountPI->stopAnimation(); if (focusPI) focusPI->stopAnimation(); if (guidePI) guidePI->stopAnimation(); m_isStarted = false; //processINDIB->setText(i18n("Start INDI")); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); } void Manager::processINDI() { if (m_isStarted == false) start(); else stop(); } bool Manager::stop() { cleanDevices(); serialPortAssistant.reset(); serialPortAssistantB->setEnabled(false); profileGroup->setEnabled(true); setWindowTitle(i18n("Ekos")); return true; } bool Manager::start() { // Don't start if it is already started before if (m_ekosStatus == Ekos::Pending || m_ekosStatus == Ekos::Success) { qCDebug(KSTARS_EKOS) << "Ekos Manager start called but current Ekos Status is" << m_ekosStatus << "Ignoring request."; return true; } if (m_LocalMode) qDeleteAll(managedDrivers); managedDrivers.clear(); // If clock was paused, unpaused it and sync time if (KStarsData::Instance()->clock()->isActive() == false) { KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); KStarsData::Instance()->clock()->start(); } reset(); // connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this]() // { // QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); // qDebug() << "Dialog was accepted!"; // }); // KSMessageBox::Instance()->questionYesNo("Do you want to proceed?", "Confirm", 21); currentProfile = getCurrentProfile(); m_LocalMode = currentProfile->isLocal(); // Load profile location if one exists updateProfileLocation(currentProfile); bool haveCCD = false, haveGuider = false; if (currentProfile->guidertype == Ekos::Guide::GUIDE_PHD2) { Options::setPHD2Host(currentProfile->guiderhost); Options::setPHD2Port(currentProfile->guiderport); } else if (currentProfile->guidertype == Ekos::Guide::GUIDE_LINGUIDER) { Options::setLinGuiderHost(currentProfile->guiderhost); Options::setLinGuiderPort(currentProfile->guiderport); } if (m_LocalMode) { DriverInfo * drv = driversList.value(currentProfile->mount()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->ccd()); if (drv != nullptr) { managedDrivers.append(drv->clone()); haveCCD = true; } Options::setGuiderType(currentProfile->guidertype); drv = driversList.value(currentProfile->guider()); if (drv != nullptr) { haveGuider = true; // If the guider and ccd are the same driver, we have two cases: // #1 Drivers that only support ONE device per driver (such as sbig) // #2 Drivers that supports multiples devices per driver (such as sx) // For #1, we modify guider_di to make a unique label for the other device with postfix "Guide" // For #2, we set guider_di to nullptr and we prompt the user to select which device is primary ccd and which is guider // since this is the only way to find out in real time. if (haveCCD && currentProfile->guider() == currentProfile->ccd()) { if (drv->getAuxInfo().value("mdpd", false).toBool() == true) { drv = nullptr; } else { drv->setUniqueLabel(drv->getLabel() + " Guide"); } } if (drv) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->ao()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->filter()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->focuser()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->dome()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->weather()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux1()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux2()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux3()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux4()); if (drv != nullptr) managedDrivers.append(drv->clone()); // Add remote drivers if we have any if (currentProfile->remotedrivers.isEmpty() == false && currentProfile->remotedrivers.contains("@")) { for (auto remoteDriver : currentProfile->remotedrivers.split(",")) { QString name, label, host("localhost"), port("7624"); QStringList properties = remoteDriver.split(QRegExp("[@:]")); if (properties.length() > 1) { name = properties[0]; host = properties[1]; if (properties.length() > 2) port = properties[2]; } DriverInfo * dv = new DriverInfo(name); dv->setRemoteHost(host); dv->setRemotePort(port); label = name; // Remove extra quotes label.remove("\""); dv->setLabel(label); dv->setUniqueLabel(label); managedDrivers.append(dv); } } if (haveCCD == false && haveGuider == false) { KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate.")); managedDrivers.clear(); return false; } nDevices = managedDrivers.count(); } else { DriverInfo * remote_indi = new DriverInfo(QString("Ekos Remote Host")); remote_indi->setHostParameters(currentProfile->host, QString::number(currentProfile->port)); remote_indi->setDriverSource(GENERATED_SOURCE); managedDrivers.append(remote_indi); haveCCD = currentProfile->drivers.contains("CCD"); haveGuider = currentProfile->drivers.contains("Guider"); Options::setGuiderType(currentProfile->guidertype); if (haveCCD == false && haveGuider == false) { KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate.")); delete (remote_indi); nDevices = 0; return false; } nDevices = currentProfile->drivers.count(); } connect(INDIListener::Instance(), &INDIListener::newDevice, this, &Ekos::Manager::processNewDevice); connect(INDIListener::Instance(), &INDIListener::newTelescope, this, &Ekos::Manager::setTelescope); connect(INDIListener::Instance(), &INDIListener::newCCD, this, &Ekos::Manager::setCCD); connect(INDIListener::Instance(), &INDIListener::newFilter, this, &Ekos::Manager::setFilter); connect(INDIListener::Instance(), &INDIListener::newFocuser, this, &Ekos::Manager::setFocuser); connect(INDIListener::Instance(), &INDIListener::newDome, this, &Ekos::Manager::setDome); connect(INDIListener::Instance(), &INDIListener::newWeather, this, &Ekos::Manager::setWeather); connect(INDIListener::Instance(), &INDIListener::newDustCap, this, &Ekos::Manager::setDustCap); connect(INDIListener::Instance(), &INDIListener::newLightBox, this, &Ekos::Manager::setLightBox); connect(INDIListener::Instance(), &INDIListener::newST4, this, &Ekos::Manager::setST4); connect(INDIListener::Instance(), &INDIListener::deviceRemoved, this, &Ekos::Manager::removeDevice, Qt::DirectConnection); #ifdef Q_OS_OSX if (m_LocalMode || currentProfile->host == "localhost") { if (isRunning("PTPCamera")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(nullptr, i18n("Ekos detected that PTP Camera is running and may prevent a Canon or Nikon camera from connecting to Ekos. Do you want to quit PTP Camera now?"), i18n("PTP Camera"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_PTPCamera"))) { //TODO is there a better way to do this. QProcess p; p.start("killall PTPCamera"); p.waitForFinished(); } } } #endif if (m_LocalMode) { if (isRunning("indiserver")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(nullptr, i18n("Ekos detected an instance of INDI server running. Do you wish to " "shut down the existing instance before starting a new one?"), i18n("INDI Server"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_existing_indiserver"))) { DriverManager::Instance()->stopAllDevices(); //TODO is there a better way to do this. QProcess p; p.start("pkill indiserver"); p.waitForFinished(); } } appendLogText(i18n("Starting INDI services...")); if (DriverManager::Instance()->startDevices(managedDrivers) == false) { INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString, QString)), this, SLOT(processServerTermination(QString, QString))); m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); if (currentProfile->autoConnect) appendLogText(i18n("INDI services started on port %1.", managedDrivers.first()->getPort())); else appendLogText( i18n("INDI services started on port %1. Please connect devices.", managedDrivers.first()->getPort())); QTimer::singleShot(MAX_LOCAL_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); } else { // If we need to use INDI Web Manager if (currentProfile->INDIWebManagerPort > 0) { appendLogText(i18n("Establishing communication with remote INDI Web Manager...")); m_RemoteManagerStart = false; if (INDI::WebManager::isOnline(currentProfile)) { INDI::WebManager::syncCustomDrivers(currentProfile); currentProfile->isStellarMate = INDI::WebManager::isStellarMate(currentProfile); if (INDI::WebManager::areDriversRunning(currentProfile) == false) { INDI::WebManager::stopProfile(currentProfile); if (INDI::WebManager::startProfile(currentProfile) == false) { appendLogText(i18n("Failed to start profile on remote INDI Web Manager.")); return false; } appendLogText(i18n("Starting profile on remote INDI Web Manager...")); m_RemoteManagerStart = true; } } else appendLogText(i18n("Warning: INDI Web Manager is not online.")); } appendLogText( i18n("Connecting to remote INDI server at %1 on port %2 ...", currentProfile->host, currentProfile->port)); qApp->processEvents(); QApplication::setOverrideCursor(Qt::WaitCursor); if (DriverManager::Instance()->connectRemoteHost(managedDrivers.first()) == false) { appendLogText(i18n("Failed to connect to remote INDI server.")); INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); QApplication::restoreOverrideCursor(); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString, QString)), this, SLOT(processServerTermination(QString, QString))); QApplication::restoreOverrideCursor(); m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); appendLogText( i18n("INDI services started. Connection to remote INDI server is successful. Waiting for devices...")); QTimer::singleShot(MAX_REMOTE_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); } connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); profileGroup->setEnabled(false); m_isStarted = true; //processINDIB->setText(i18n("Stop INDI")); processINDIB->setIcon(QIcon::fromTheme("media-playback-stop")); processINDIB->setToolTip(i18n("Stop")); setWindowTitle(i18n("Ekos - %1 Profile", currentProfile->name)); return true; } void Manager::checkINDITimeout() { // Don't check anything unless we're still pending if (m_ekosStatus != Ekos::Pending) return; if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); return; } if (m_LocalMode) { QStringList remainingDevices; foreach (DriverInfo * drv, managedDrivers) { if (drv->getDevices().count() == 0) remainingDevices << QString("+ %1").arg( drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName()); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish:\n%1\nPlease ensure the device is connected and powered on.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish the following devices:\n%1\nPlease ensure each device is connected " "and powered on.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } else { QStringList remainingDevices; for (auto &driver : currentProfile->drivers.values()) { bool driverFound = false; for (auto &device : genericDevices) { if (device->getBaseDevice()->getDriverName() == driver) { driverFound = true; break; } } if (driverFound == false) remainingDevices << QString("+ %1").arg(driver); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish remote device:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish remote devices:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } m_ekosStatus = Ekos::Error; } void Manager::connectDevices() { // Check if already connected int nConnected = 0; Ekos::CommunicationStatus previousStatus = m_indiStatus; for (auto &device : genericDevices) { if (device->isConnected()) nConnected++; } if (genericDevices.count() == nConnected) { m_indiStatus = Ekos::Success; emit indiStatusChanged(m_indiStatus); return; } m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName(); device->Connect(); } connectB->setEnabled(false); disconnectB->setEnabled(true); appendLogText(i18n("Connecting INDI devices...")); } void Manager::disconnectDevices() { for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName(); device->Disconnect(); } appendLogText(i18n("Disconnecting INDI devices...")); } void Manager::processServerTermination(const QString &host, const QString &port) { if ((m_LocalMode && managedDrivers.first()->getPort() == port) || (currentProfile->host == host && currentProfile->port == port.toInt())) { cleanDevices(false); } } void Manager::cleanDevices(bool stopDrivers) { if (m_ekosStatus == Ekos::Idle) return; INDIListener::Instance()->disconnect(this); DriverManager::Instance()->disconnect(this); if (managedDrivers.isEmpty() == false) { if (m_LocalMode) { if (stopDrivers) DriverManager::Instance()->stopDevices(managedDrivers); } else { if (stopDrivers) DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first()); if (m_RemoteManagerStart && currentProfile->INDIWebManagerPort != -1) { INDI::WebManager::stopProfile(currentProfile); m_RemoteManagerStart = false; } } } reset(); profileGroup->setEnabled(true); appendLogText(i18n("INDI services stopped.")); } void Manager::processNewDevice(ISD::GDInterface * devInterface) { qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << devInterface->getDeviceName(); Ekos::CommunicationStatus previousStatus = m_indiStatus; for(auto &device : genericDevices) { if (!strcmp(device->getDeviceName(), devInterface->getDeviceName())) { qCWarning(KSTARS_EKOS) << "Found duplicate device, ignoring..."; return; } } // Always reset INDI Connection status if we receive a new device m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); genericDevices.append(devInterface); nDevices--; connect(devInterface, &ISD::GDInterface::Connected, this, &Ekos::Manager::deviceConnected); connect(devInterface, &ISD::GDInterface::Disconnected, this, &Ekos::Manager::deviceDisconnected); connect(devInterface, &ISD::GDInterface::propertyDefined, this, &Ekos::Manager::processNewProperty); connect(devInterface, &ISD::GDInterface::interfaceDefined, this, &Ekos::Manager::syncActiveDevices); if (currentProfile->isStellarMate) { connect(devInterface, &ISD::GDInterface::systemPortDetected, [this, devInterface]() { if (!serialPortAssistant) { serialPortAssistant.reset(new SerialPortAssistant(currentProfile, this)); serialPortAssistantB->setEnabled(true); } uint32_t driverInterface = devInterface->getDriverInterface(); // Ignore CCD interface if (driverInterface & INDI::BaseDevice::CCD_INTERFACE) return; if (driverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE || driverInterface & INDI::BaseDevice::FOCUSER_INTERFACE || driverInterface & INDI::BaseDevice::FILTER_INTERFACE || driverInterface & INDI::BaseDevice::AUX_INTERFACE || driverInterface & INDI::BaseDevice::GPS_INTERFACE) serialPortAssistant->addDevice(devInterface); if (Options::autoLoadSerialAssistant()) serialPortAssistant->show(); }); } if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); //controlPanelB->setEnabled(true); if (m_LocalMode == false && nDevices == 0) { if (currentProfile->autoConnect) appendLogText(i18n("Remote devices established.")); else appendLogText(i18n("Remote devices established. Please connect devices.")); } } } void Manager::deviceConnected() { connectB->setEnabled(false); disconnectB->setEnabled(true); processINDIB->setEnabled(false); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (Options::verboseLogging()) { ISD::GDInterface * device = qobject_cast(sender()); qCInfo(KSTARS_EKOS) << device->getDeviceName() << "Version:" << device->getDriverVersion() << "Interface:" << device->getDriverInterface() << "is connected."; } int nConnectedDevices = 0; foreach (ISD::GDInterface * device, genericDevices) { if (device->isConnected()) nConnectedDevices++; } qCDebug(KSTARS_EKOS) << nConnectedDevices << " devices connected out of " << genericDevices.count(); if (nConnectedDevices >= currentProfile->drivers.count()) //if (nConnectedDevices >= genericDevices.count()) { m_indiStatus = Ekos::Success; qCInfo(KSTARS_EKOS) << "All INDI devices are now connected."; } else m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); ISD::GDInterface * dev = static_cast(sender()); if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE) { if (mountProcess.get() != nullptr) { mountProcess->setEnabled(true); if (alignProcess.get() != nullptr) alignProcess->setEnabled(true); } } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(true); if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); if (alignProcess.get() != nullptr) { if (mountProcess.get() && mountProcess->isEnabled()) alignProcess->setEnabled(true); else alignProcess->setEnabled(false); } if (guideProcess.get() != nullptr) guideProcess->setEnabled(true); } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); } if (Options::neverLoadConfig()) return; INDIConfig tConfig = Options::loadConfigOnConnection() ? LOAD_LAST_CONFIG : LOAD_DEFAULT_CONFIG; foreach (ISD::GDInterface * device, genericDevices) { if (device == dev) { connect(dev, &ISD::GDInterface::switchUpdated, this, &Ekos::Manager::watchDebugProperty); ISwitchVectorProperty * configProp = device->getBaseDevice()->getSwitch("CONFIG_PROCESS"); if (configProp && configProp->s == IPS_IDLE) device->setConfig(tConfig); break; } } } void Manager::deviceDisconnected() { ISD::GDInterface * dev = static_cast(sender()); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (dev != nullptr) { if (dev->getState("CONNECTION") == IPS_ALERT) m_indiStatus = Ekos::Error; else if (dev->getState("CONNECTION") == IPS_BUSY) m_indiStatus = Ekos::Pending; else m_indiStatus = Ekos::Idle; if (Options::verboseLogging()) qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected."; appendLogText(i18n("%1 is disconnected.", dev->getDeviceName())); } else m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); processINDIB->setEnabled(true); if (dev != nullptr && dev->getBaseDevice() && (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) { if (mountProcess.get() != nullptr) mountProcess->setEnabled(false); } // Do not disable modules on device connection loss, let them handle it /* else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(false); if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); if (alignProcess.get() != nullptr) alignProcess->setEnabled(false); if (guideProcess.get() != nullptr) guideProcess->setEnabled(false); } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); }*/ } void Manager::setTelescope(ISD::GDInterface * scopeDevice) { //mount = scopeDevice; managedDevices[KSTARS_TELESCOPE] = scopeDevice; appendLogText(i18n("%1 is online.", scopeDevice->getDeviceName())); connect(scopeDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); initMount(); mountProcess->setTelescope(scopeDevice); double primaryScopeFL = 0, primaryScopeAperture = 0, guideScopeFL = 0, guideScopeAperture = 0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver mountProcess->setTelescopeInfo(QList() << primaryScopeFL << primaryScopeAperture << guideScopeFL << guideScopeAperture); if (guideProcess.get() != nullptr) { guideProcess->setTelescope(scopeDevice); guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(scopeDevice); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } // if (domeProcess.get() != nullptr) // domeProcess->setTelescope(scopeDevice); ekosLiveClient->message()->sendMounts(); ekosLiveClient->message()->sendScopes(); } void Manager::setCCD(ISD::GDInterface * ccdDevice) { // No duplicates for (auto oneCCD : findDevices(KSTARS_CCD)) if (oneCCD == ccdDevice) return; managedDevices.insertMulti(KSTARS_CCD, ccdDevice); initCapture(); captureProcess->setEnabled(true); captureProcess->addCCD(ccdDevice); QString primaryCCD, guiderCCD; // Only look for primary & guider CCDs if we can tell a difference between them // otherwise rely on saved options if (currentProfile->ccd() != currentProfile->guider()) { foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (QString(device->getDeviceName()).startsWith(currentProfile->ccd(), Qt::CaseInsensitive)) primaryCCD = QString(device->getDeviceName()); else if (QString(device->getDeviceName()).startsWith(currentProfile->guider(), Qt::CaseInsensitive)) guiderCCD = QString(device->getDeviceName()); } } bool rc = false; if (Options::defaultCaptureCCD().isEmpty() == false) rc = captureProcess->setCamera(Options::defaultCaptureCCD()); if (rc == false && primaryCCD.isEmpty() == false) captureProcess->setCamera(primaryCCD); initFocus(); focusProcess->addCCD(ccdDevice); rc = false; if (Options::defaultFocusCCD().isEmpty() == false) rc = focusProcess->setCamera(Options::defaultFocusCCD()); if (rc == false && primaryCCD.isEmpty() == false) focusProcess->setCamera(primaryCCD); initAlign(); alignProcess->addCCD(ccdDevice); rc = false; if (Options::defaultAlignCCD().isEmpty() == false) rc = alignProcess->setCamera(Options::defaultAlignCCD()); if (rc == false && primaryCCD.isEmpty() == false) alignProcess->setCamera(primaryCCD); initGuide(); guideProcess->addCamera(ccdDevice); rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false && guiderCCD.isEmpty() == false) guideProcess->setCamera(guiderCCD); appendLogText(i18n("%1 is online.", ccdDevice->getDeviceName())); connect(ccdDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); if (managedDevices.contains(KSTARS_TELESCOPE)) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); } } void Manager::setFilter(ISD::GDInterface * filterDevice) { // No duplicates if (findDevices(KSTARS_FILTER).contains(filterDevice) == false) managedDevices.insertMulti(KSTARS_FILTER, filterDevice); appendLogText(i18n("%1 filter is online.", filterDevice->getDeviceName())); initCapture(); connect(filterDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); connect(filterDevice, SIGNAL(textUpdated(ITextVectorProperty *)), this, SLOT(processNewText(ITextVectorProperty *)), Qt::UniqueConnection); captureProcess->addFilter(filterDevice); initFocus(); focusProcess->addFilter(filterDevice); initAlign(); alignProcess->addFilter(filterDevice); if (Options::defaultAlignFW().isEmpty() == false) alignProcess->setFilterWheel(Options::defaultAlignFW()); } void Manager::setFocuser(ISD::GDInterface * focuserDevice) { // No duplicates if (findDevices(KSTARS_FOCUSER).contains(focuserDevice) == false) managedDevices.insertMulti(KSTARS_FOCUSER, focuserDevice); initCapture(); initFocus(); focusProcess->addFocuser(focuserDevice); if (Options::defaultFocusFocuser().isEmpty() == false) focusProcess->setFocuser(Options::defaultFocusFocuser()); appendLogText(i18n("%1 focuser is online.", focuserDevice->getDeviceName())); } void Manager::setDome(ISD::GDInterface * domeDevice) { managedDevices[KSTARS_DOME] = domeDevice; initDome(); domeProcess->setDome(domeDevice); if (captureProcess.get() != nullptr) captureProcess->setDome(domeDevice); if (alignProcess.get() != nullptr) alignProcess->setDome(domeDevice); // if (managedDevices.contains(KSTARS_TELESCOPE)) // domeProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); appendLogText(i18n("%1 is online.", domeDevice->getDeviceName())); } void Manager::setWeather(ISD::GDInterface * weatherDevice) { managedDevices[KSTARS_WEATHER] = weatherDevice; initWeather(); weatherProcess->setWeather(weatherDevice); appendLogText(i18n("%1 is online.", weatherDevice->getDeviceName())); } void Manager::setDustCap(ISD::GDInterface * dustCapDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(dustCapDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, dustCapDevice); initDustCap(); dustCapProcess->setDustCap(dustCapDevice); appendLogText(i18n("%1 is online.", dustCapDevice->getDeviceName())); if (captureProcess.get() != nullptr) captureProcess->setDustCap(dustCapDevice); DarkLibrary::Instance()->setRemoteCap(dustCapDevice); } void Manager::setLightBox(ISD::GDInterface * lightBoxDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(lightBoxDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, lightBoxDevice); if (captureProcess.get() != nullptr) captureProcess->setLightBox(lightBoxDevice); } void Manager::removeDevice(ISD::GDInterface * devInterface) { // switch (devInterface->getType()) // { // case KSTARS_CCD: // removeTabs(); // break; // default: // break; // } if (alignProcess) alignProcess->removeDevice(devInterface); if (captureProcess) captureProcess->removeDevice(devInterface); if (focusProcess) focusProcess->removeDevice(devInterface); if (mountProcess) mountProcess->removeDevice(devInterface); if (guideProcess) guideProcess->removeDevice(devInterface); if (domeProcess) domeProcess->removeDevice(devInterface); if (weatherProcess) weatherProcess->removeDevice(devInterface); if (dustCapProcess) { dustCapProcess->removeDevice(devInterface); DarkLibrary::Instance()->removeDevice(devInterface); } appendLogText(i18n("%1 is offline.", devInterface->getDeviceName())); // #1 Remove from Generic Devices // Generic devices are ALL the devices we receive from INDI server // Whether Ekos cares about them (i.e. selected equipment) or extra devices we // do not care about for (auto &device : genericDevices) { if (!strcmp(device->getDeviceName(), devInterface->getDeviceName())) { genericDevices.removeOne(device); } } // #2 Remove from Ekos Managed Device // Managed devices are devices selected by the user in the device profile for (auto &device : managedDevices.values()) { if (!strcmp(device->getDeviceName(), devInterface->getDeviceName())) { managedDevices.remove(managedDevices.key(device)); if (managedDevices.count() == 0) cleanDevices(); //break; } } if (managedDevices.isEmpty()) removeTabs(); } void Manager::processNewText(ITextVectorProperty * tvp) { if (!strcmp(tvp->name, "FILTER_NAME")) { ekosLiveClient.get()->message()->sendFilterWheels(); } } void Manager::processNewNumber(INumberVectorProperty * nvp) { if (!strcmp(nvp->name, "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(nvp->name, "CCD_INFO") || !strcmp(nvp->name, "GUIDER_INFO") || !strcmp(nvp->name, "CCD_FRAME") || !strcmp(nvp->name, "GUIDER_FRAME")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } /* if (!strcmp(nvp->name, "FILTER_SLOT")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); } */ } void Manager::processNewProperty(INDI::Property * prop) { ISD::GenericDevice * deviceInterface = qobject_cast(sender()); if (!strcmp(prop->getName(), "CONNECTION") && currentProfile->autoConnect) { // Check if we need to do any mappings const QString port = m_ProfileMapping.value(QString(deviceInterface->getDeviceName())).toString(); // If we don't have port mapping, then we connect immediately. if (port.isEmpty()) deviceInterface->Connect(); return; } if (!strcmp(prop->getName(), "DEVICE_PORT")) { // Check if we need to do any mappings const QString port = m_ProfileMapping.value(QString(deviceInterface->getDeviceName())).toString(); if (!port.isEmpty()) { ITextVectorProperty *tvp = prop->getText(); IUSaveText(&(tvp->tp[0]), port.toLatin1().data()); deviceInterface->getDriverInfo()->getClientManager()->sendNewText(tvp); // Now connect if we need to. if (currentProfile->autoConnect) deviceInterface->Connect(); return; } } // Check if we need to turn on DEBUG for logging purposes if (!strcmp(prop->getName(), "DEBUG")) { uint16_t interface = deviceInterface->getBaseDevice()->getDriverInterface(); if ( opsLogs->getINDIDebugInterface() & interface ) { // Check if we need to enable debug logging for the INDI drivers. ISwitchVectorProperty * debugSP = prop->getSwitch(); debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); } } // Handle debug levels for logging purposes if (!strcmp(prop->getName(), "DEBUG_LEVEL")) { uint16_t interface = deviceInterface->getBaseDevice()->getDriverInterface(); // Check if the logging option for the specific device class is on and if the device interface matches it. if ( opsLogs->getINDIDebugInterface() & interface ) { // Turn on everything ISwitchVectorProperty * debugLevel = prop->getSwitch(); for (int i = 0; i < debugLevel->nsp; i++) debugLevel->sp[i].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugLevel); } } if (!strcmp(prop->getName(), "ACTIVE_DEVICES")) { if (deviceInterface->getDriverInterface() > 0) syncActiveDevices(); } if (!strcmp(prop->getName(), "TELESCOPE_INFO") || !strcmp(prop->getName(), "TELESCOPE_SLEW_RATE") || !strcmp(prop->getName(), "TELESCOPE_PARK")) { ekosLiveClient.get()->message()->sendMounts(); ekosLiveClient.get()->message()->sendScopes(); } if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "CCD_TEMPERATURE") || !strcmp(prop->getName(), "CCD_ISO") || !strcmp(prop->getName(), "CCD_GAIN") || !strcmp(prop->getName(), "CCD_CONTROLS")) { ekosLiveClient.get()->message()->sendCameras(); ekosLiveClient.get()->media()->registerCameras(); } if (!strcmp(prop->getName(), "ABS_DOME_POSITION") || !strcmp(prop->getName(), "DOME_ABORT_MOTION") || !strcmp(prop->getName(), "DOME_PARK")) { ekosLiveClient.get()->message()->sendDomes(); } if (!strcmp(prop->getName(), "CAP_PARK") || !strcmp(prop->getName(), "FLAT_LIGHT_CONTROL")) { ekosLiveClient.get()->message()->sendCaps(); } if (!strcmp(prop->getName(), "FILTER_NAME")) ekosLiveClient.get()->message()->sendFilterWheels(); if (!strcmp(prop->getName(), "FILTER_NAME")) filterManager.data()->initFilterProperties(); if (!strcmp(prop->getName(), "CONFIRM_FILTER_SET")) filterManager.data()->initFilterProperties(); if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "GUIDER_INFO")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } if (!strcmp(prop->getName(), "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(prop->getName(), "GUIDER_EXPOSURE")) { foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { initCapture(); initGuide(); useGuideHead = true; captureProcess->addGuideHead(device); guideProcess->addGuideHead(device); bool rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false) guideProcess->setCamera(QString(device->getDeviceName()) + QString(" Guider")); return; } } return; } if (!strcmp(prop->getName(), "CCD_FRAME_TYPE")) { if (captureProcess.get() != nullptr) { foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { captureProcess->syncFrameType(device); return; } } } return; } if (!strcmp(prop->getName(), "CCD_ISO")) { if (captureProcess.get() != nullptr) captureProcess->checkCCD(); return; } if (!strcmp(prop->getName(), "TELESCOPE_PARK") && managedDevices.contains(KSTARS_TELESCOPE)) { if (captureProcess.get() != nullptr) captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); if (mountProcess.get() != nullptr) mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); return; } /* if (!strcmp(prop->getName(), "FILTER_NAME")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); return; } */ if (!strcmp(prop->getName(), "ASTROMETRY_SOLVER")) { foreach (ISD::GDInterface * device, genericDevices) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { initAlign(); alignProcess->setAstrometryDevice(device); break; } } } if (!strcmp(prop->getName(), "ABS_ROTATOR_ANGLE")) { managedDevices[KSTARS_ROTATOR] = deviceInterface; if (captureProcess.get() != nullptr) captureProcess->setRotator(deviceInterface); if (alignProcess.get() != nullptr) alignProcess->setRotator(deviceInterface); } if (!strcmp(prop->getName(), "GPS_REFRESH")) { managedDevices.insertMulti(KSTARS_AUXILIARY, deviceInterface); if (mountProcess.get() != nullptr) mountProcess->setGPS(deviceInterface); } if (focusProcess.get() != nullptr && strstr(prop->getName(), "FOCUS_")) { focusProcess->checkFocuser(); } } QList Manager::getAllDevices() { QList deviceList; QMapIterator i(managedDevices); while (i.hasNext()) { i.next(); deviceList.append(i.value()); } return deviceList; } QList Manager::findDevices(DeviceFamily type) { QList deviceList; QMapIterator i(managedDevices); while (i.hasNext()) { i.next(); if (i.key() == type) deviceList.append(i.value()); } return deviceList; } QList Manager::findDevicesByInterface(uint32_t interface) { QList deviceList; for (const auto dev : genericDevices) { uint32_t devInterface = dev->getDriverInterface(); if (devInterface & interface) deviceList.append(dev); } return deviceList; } void Manager::processTabChange() { QWidget * currentWidget = toolsWidget->currentWidget(); //if (focusProcess.get() != nullptr && currentWidget != focusProcess) //focusProcess->resetFrame(); if (alignProcess.get() && alignProcess.get() == currentWidget) { if (alignProcess->isEnabled() == false && captureProcess->isEnabled()) { if (managedDevices[KSTARS_CCD]->isConnected() && managedDevices.contains(KSTARS_TELESCOPE)) { if (alignProcess->isParserOK()) alignProcess->setEnabled(true); //#ifdef Q_OS_WIN else { // If current setting is remote astrometry and profile doesn't contain // remote astrometry, then we switch to online solver. Otherwise, the whole align // module remains disabled and the user cannot change re-enable it since he cannot select online solver ProfileInfo * pi = getCurrentProfile(); if (Options::solverType() == Ekos::Align::SOLVER_REMOTE && pi->aux1() != "Astrometry" && pi->aux2() != "Astrometry" && pi->aux3() != "Astrometry" && pi->aux4() != "Astrometry") { Options::setSolverType(Ekos::Align::SOLVER_ONLINE); alignModule()->setSolverType(Ekos::Align::SOLVER_ONLINE); alignProcess->setEnabled(true); } } //#endif } } alignProcess->checkCCD(); } else if (captureProcess.get() != nullptr && currentWidget == captureProcess.get()) { captureProcess->checkCCD(); } else if (focusProcess.get() != nullptr && currentWidget == focusProcess.get()) { focusProcess->checkCCD(); } else if (guideProcess.get() != nullptr && currentWidget == guideProcess.get()) { guideProcess->checkCCD(); } updateLog(); } void Manager::updateLog() { //if (enableLoggingCheck->isChecked() == false) //return; QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) ekosLogOut->setPlainText(m_LogText.join("\n")); else if (currentWidget == alignProcess.get()) ekosLogOut->setPlainText(alignProcess->getLogText()); else if (currentWidget == captureProcess.get()) ekosLogOut->setPlainText(captureProcess->getLogText()); else if (currentWidget == focusProcess.get()) ekosLogOut->setPlainText(focusProcess->getLogText()); else if (currentWidget == guideProcess.get()) ekosLogOut->setPlainText(guideProcess->getLogText()); else if (currentWidget == mountProcess.get()) ekosLogOut->setPlainText(mountProcess->getLogText()); else if (currentWidget == schedulerProcess.get()) ekosLogOut->setPlainText(schedulerProcess->getLogText()); else if (currentWidget == observatoryProcess.get()) ekosLogOut->setPlainText(observatoryProcess->getLogText()); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif } void Manager::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS) << text; emit newLog(text); updateLog(); } void Manager::clearLog() { QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) { m_LogText.clear(); updateLog(); } else if (currentWidget == alignProcess.get()) alignProcess->clearLog(); else if (currentWidget == captureProcess.get()) captureProcess->clearLog(); else if (currentWidget == focusProcess.get()) focusProcess->clearLog(); else if (currentWidget == guideProcess.get()) guideProcess->clearLog(); else if (currentWidget == mountProcess.get()) mountProcess->clearLog(); else if (currentWidget == schedulerProcess.get()) schedulerProcess->clearLog(); else if (currentWidget == observatoryProcess.get()) observatoryProcess->clearLog(); } void Manager::initCapture() { if (captureProcess.get() != nullptr) return; captureProcess.reset(new Ekos::Capture()); captureProcess->setEnabled(false); int index = toolsWidget->addTab(captureProcess.get(), QIcon(":/icons/ekos_ccd.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18nc("Charge-Coupled Device", "CCD")); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } connect(captureProcess.get(), &Ekos::Capture::newLog, this, &Ekos::Manager::updateLog); connect(captureProcess.get(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus); connect(captureProcess.get(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress); connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString & filename, const QString & previewFITS) { if (Options::useSummaryPreview() && QFile::exists(filename)) { if (Options::autoImageToFITS()) { if (previewFITS.isEmpty() == false) summaryPreview->loadFITS(previewFITS); } else summaryPreview->loadFITS(filename); } }); connect(captureProcess.get(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress); captureGroup->setEnabled(true); sequenceProgress->setEnabled(true); captureProgress->setEnabled(true); imageProgress->setEnabled(true); captureProcess->setFilterManager(filterManager); if (!capturePI) { capturePI = new QProgressIndicator(captureProcess.get()); captureStatusLayout->insertWidget(0, capturePI); } foreach (ISD::GDInterface * device, findDevices(KSTARS_AUXILIARY)) { if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::DUSTCAP_INTERFACE) captureProcess->setDustCap(device); if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::LIGHTBOX_INTERFACE) captureProcess->setLightBox(device); } if (managedDevices.contains(KSTARS_DOME)) { captureProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { captureProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Capture"); } void Manager::initAlign() { if (alignProcess.get() != nullptr) return; alignProcess.reset(new Ekos::Align(currentProfile)); double primaryScopeFL = 0, primaryScopeAperture = 0, guideScopeFL = 0, guideScopeAperture = 0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setEnabled(false); int index = toolsWidget->addTab(alignProcess.get(), QIcon(":/icons/ekos_align.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Align")); connect(alignProcess.get(), &Ekos::Align::newLog, this, &Ekos::Manager::updateLog); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } alignProcess->setFilterManager(filterManager); if (managedDevices.contains(KSTARS_DOME)) { alignProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { alignProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Align"); } void Manager::initFocus() { if (focusProcess.get() != nullptr) return; focusProcess.reset(new Ekos::Focus()); int index = toolsWidget->addTab(focusProcess.get(), QIcon(":/icons/ekos_focus.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Focus")); // Focus <---> Manager connections connect(focusProcess.get(), &Ekos::Focus::newLog, this, &Ekos::Manager::updateLog); connect(focusProcess.get(), &Ekos::Focus::newStatus, this, &Ekos::Manager::setFocusStatus); connect(focusProcess.get(), &Ekos::Focus::newStarPixmap, this, &Ekos::Manager::updateFocusStarPixmap); connect(focusProcess.get(), &Ekos::Focus::newProfilePixmap, this, &Ekos::Manager::updateFocusProfilePixmap); connect(focusProcess.get(), &Ekos::Focus::newHFR, this, &Ekos::Manager::updateCurrentHFR); // Focus <---> Filter Manager connections focusProcess->setFilterManager(filterManager); connect(filterManager.data(), &Ekos::FilterManager::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::newStatus, filterManager.data(), &Ekos::FilterManager::setFocusStatus, Qt::UniqueConnection); connect(filterManager.data(), &Ekos::FilterManager::newFocusOffset, focusProcess.get(), &Ekos::Focus::adjustFocusOffset, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::focusPositionAdjusted, filterManager.data(), &Ekos::FilterManager::setFocusOffsetComplete, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::absolutePositionChanged, filterManager.data(), &Ekos::FilterManager::setFocusAbsolutePosition, Qt::UniqueConnection); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } focusGroup->setEnabled(true); if (!focusPI) { focusPI = new QProgressIndicator(focusProcess.get()); focusStatusLayout->insertWidget(0, focusPI); } connectModules(); emit newModule("Focus"); } void Manager::updateCurrentHFR(double newHFR, int position) { currentHFR->setText(QString("%1").arg(newHFR, 0, 'f', 2) + " px"); QJsonObject cStatus = { {"hfr", newHFR}, {"pos", position} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void Manager::updateSigmas(double ra, double de) { errRA->setText(QString::number(ra, 'f', 2) + "\""); errDEC->setText(QString::number(de, 'f', 2) + "\""); QJsonObject cStatus = { {"rarms", ra}, {"derms", de} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void Manager::initMount() { if (mountProcess.get() != nullptr) return; mountProcess.reset(new Ekos::Mount()); int index = toolsWidget->addTab(mountProcess.get(), QIcon(":/icons/ekos_mount.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Mount")); connect(mountProcess.get(), &Ekos::Mount::newLog, this, &Ekos::Manager::updateLog); connect(mountProcess.get(), &Ekos::Mount::newCoords, this, &Ekos::Manager::updateMountCoords); connect(mountProcess.get(), &Ekos::Mount::newStatus, this, &Ekos::Manager::updateMountStatus); connect(mountProcess.get(), &Ekos::Mount::newTarget, [&](const QString & target) { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); connect(mountProcess.get(), &Ekos::Mount::slewRateChanged, [&](int slewRate) { QJsonObject status = { { "slewRate", slewRate} }; ekosLiveClient.get()->message()->updateMountStatus(status); } ); foreach (ISD::GDInterface * device, findDevices(KSTARS_AUXILIARY)) { if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE) mountProcess->setGPS(device); } if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } if (!mountPI) { mountPI = new QProgressIndicator(mountProcess.get()); mountStatusLayout->insertWidget(0, mountPI); } mountGroup->setEnabled(true); connectModules(); emit newModule("Mount"); } void Manager::initGuide() { if (guideProcess.get() == nullptr) { guideProcess.reset(new Ekos::Guide()); double primaryScopeFL = 0, primaryScopeAperture = 0, guideScopeFL = 0, guideScopeAperture = 0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } //if ( (haveGuider || ccdCount > 1 || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess) == -1) if ((findDevices(KSTARS_CCD).isEmpty() == false || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess.get()) == -1) { //if (mount && mount->isConnected()) if (managedDevices.contains(KSTARS_TELESCOPE) && managedDevices[KSTARS_TELESCOPE]->isConnected()) guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); int index = toolsWidget->addTab(guideProcess.get(), QIcon(":/icons/ekos_guide.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Guide")); connect(guideProcess.get(), &Ekos::Guide::newLog, this, &Ekos::Manager::updateLog); guideGroup->setEnabled(true); if (!guidePI) { guidePI = new QProgressIndicator(guideProcess.get()); guideStatusLayout->insertWidget(0, guidePI); } connect(guideProcess.get(), &Ekos::Guide::newStatus, this, &Ekos::Manager::updateGuideStatus); connect(guideProcess.get(), &Ekos::Guide::newStarPixmap, this, &Ekos::Manager::updateGuideStarPixmap); connect(guideProcess.get(), &Ekos::Guide::newProfilePixmap, this, &Ekos::Manager::updateGuideProfilePixmap); connect(guideProcess.get(), &Ekos::Guide::newAxisSigma, this, &Ekos::Manager::updateSigmas); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } } connectModules(); emit newModule("Guide"); } void Manager::initDome() { if (domeProcess.get() != nullptr) return; domeProcess.reset(new Ekos::Dome()); connect(domeProcess.get(), &Ekos::Dome::newStatus, [&](ISD::Dome::Status newStatus) { QJsonObject status = { { "status", ISD::Dome::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); connect(domeProcess.get(), &Ekos::Dome::azimuthPositionChanged, [&](double pos) { QJsonObject status = { { "az", pos} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); initObservatory(nullptr, domeProcess.get()); emit newModule("Dome"); ekosLiveClient->message()->sendDomes(); } void Manager::initWeather() { if (weatherProcess.get() != nullptr) return; weatherProcess.reset(new Ekos::Weather()); initObservatory(weatherProcess.get(), nullptr); emit newModule("Weather"); } void Manager::initObservatory(Weather *weather, Dome *dome) { if (observatoryProcess.get() == nullptr) { // Initialize the Observatory Module observatoryProcess.reset(new Ekos::Observatory()); int index = toolsWidget->addTab(observatoryProcess.get(), QIcon(":/icons/ekos_observatory.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Observatory")); connect(observatoryProcess.get(), &Ekos::Observatory::newLog, this, &Ekos::Manager::updateLog); } Observatory *obs = observatoryProcess.get(); if (weather != nullptr) obs->getWeatherModel()->initModel(weather); if (dome != nullptr) obs->getDomeModel()->initModel(dome); emit newModule("Observatory"); } void Manager::initDustCap() { if (dustCapProcess.get() != nullptr) return; dustCapProcess.reset(new Ekos::DustCap()); connect(dustCapProcess.get(), &Ekos::DustCap::newStatus, [&](ISD::DustCap::Status newStatus) { QJsonObject status = { { "status", ISD::DustCap::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); connect(dustCapProcess.get(), &Ekos::DustCap::lightToggled, [&](bool enabled) { QJsonObject status = { { "lightS", enabled} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); connect(dustCapProcess.get(), &Ekos::DustCap::lightIntensityChanged, [&](uint16_t value) { QJsonObject status = { { "lightB", value} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); emit newModule("DustCap"); ekosLiveClient->message()->sendCaps(); } void Manager::setST4(ISD::ST4 * st4Driver) { appendLogText(i18n("Guider port from %1 is ready.", st4Driver->getDeviceName())); useST4 = true; initGuide(); guideProcess->addST4(st4Driver); if (Options::defaultST4Driver().isEmpty() == false) guideProcess->setST4(Options::defaultST4Driver()); } void Manager::removeTabs() { disconnect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange); for (int i = 2; i < toolsWidget->count(); i++) toolsWidget->removeTab(i); alignProcess.reset(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); mountProcess.reset(); domeProcess.reset(); weatherProcess.reset(); observatoryProcess.reset(); dustCapProcess.reset(); managedDevices.clear(); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); } bool Manager::isRunning(const QString &process) { QProcess ps; #ifdef Q_OS_OSX ps.start("pgrep", QStringList() << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.length() > 0; #else ps.start("ps", QStringList() << "-o" << "comm" << "--no-headers" << "-C" << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.contains(process); #endif } void Manager::addObjectToScheduler(SkyObject * object) { if (schedulerProcess.get() != nullptr) schedulerProcess->addObject(object); } QString Manager::getCurrentJobName() { return schedulerProcess->getCurrentJobName(); } bool Manager::setProfile(const QString &profileName) { int index = profileCombo->findText(profileName); if (index < 0) return false; profileCombo->setCurrentIndex(index); return true; } void Manager::editNamedProfile(const QJsonObject &profileInfo) { ProfileEditor editor(this); setProfile(profileInfo["name"].toString()); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); editor.setSettings(profileInfo); editor.saveProfile(); } void Manager::addNamedProfile(const QJsonObject &profileInfo) { ProfileEditor editor(this); editor.setSettings(profileInfo); editor.saveProfile(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); currentProfile = getCurrentProfile(); } void Manager::deleteNamedProfile(const QString &name) { currentProfile = getCurrentProfile(); for (auto &pi : profiles) { // Do not delete an actively running profile // Do not delete simulator profile if (pi->name == "Simulators" || pi->name != name || (pi.get() == currentProfile && ekosStatus() != Idle)) continue; KStarsData::Instance()->userdb()->DeleteProfile(pi.get()); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); return; } } QJsonObject Manager::getNamedProfile(const QString &name) { QJsonObject profileInfo; // Get current profile for (auto &pi : profiles) { if (name == pi->name) return pi->toJson(); } return QJsonObject(); } QStringList Manager::getProfiles() { QStringList profiles; for (int i = 0; i < profileCombo->count(); i++) profiles << profileCombo->itemText(i); return profiles; } void Manager::addProfile() { ProfileEditor editor(this); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } void Manager::editProfile() { ProfileEditor editor(this); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); if (editor.exec() == QDialog::Accepted) { int currentIndex = profileCombo->currentIndex(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(currentIndex); } currentProfile = getCurrentProfile(); } void Manager::deleteProfile() { currentProfile = getCurrentProfile(); if (currentProfile->name == "Simulators") return; auto executeDeleteProfile = [&]() { KStarsData::Instance()->userdb()->DeleteProfile(currentProfile); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); }; connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeDeleteProfile]() { //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); KSMessageBox::Instance()->disconnect(this); executeDeleteProfile(); }); KSMessageBox::Instance()->questionYesNo(i18n("Are you sure you want to delete the profile?"), i18n("Confirm Delete")); } void Manager::wizardProfile() { ProfileWizard wz; if (wz.exec() != QDialog::Accepted) return; ProfileEditor editor(this); editor.setProfileName(wz.profileName); editor.setAuxDrivers(wz.selectedAuxDrivers()); if (wz.useInternalServer == false) editor.setHostPort(wz.host, wz.port); editor.setWebManager(wz.useWebManager); editor.setGuiderType(wz.selectedExternalGuider()); // Disable connection options editor.setConnectionOptionsEnabled(false); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } ProfileInfo * Manager::getCurrentProfile() { ProfileInfo * currProfile = nullptr; // Get current profile for (auto &pi : profiles) { if (profileCombo->currentText() == pi->name) { currProfile = pi.get(); break; } } return currProfile; } void Manager::updateProfileLocation(ProfileInfo * pi) { if (pi->city.isEmpty() == false) { bool cityFound = KStars::Instance()->setGeoLocation(pi->city, pi->province, pi->country); if (cityFound) appendLogText(i18n("Site location updated to %1.", KStarsData::Instance()->geo()->fullName())); else appendLogText(i18n("Failed to update site location to %1. City not found.", KStarsData::Instance()->geo()->fullName())); } } void Manager::updateMountStatus(ISD::Telescope::Status status) { static ISD::Telescope::Status lastStatus = ISD::Telescope::MOUNT_IDLE; if (status == lastStatus) return; lastStatus = status; mountStatus->setText(dynamic_cast(managedDevices[KSTARS_TELESCOPE])->getStatusString(status)); mountStatus->setStyleSheet(QString()); switch (status) { case ISD::Telescope::MOUNT_PARKING: case ISD::Telescope::MOUNT_SLEWING: case ISD::Telescope::MOUNT_MOVING: mountPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (mountPI->isAnimated() == false) mountPI->startAnimation(); break; case ISD::Telescope::MOUNT_TRACKING: mountPI->setColor(Qt::darkGreen); if (mountPI->isAnimated() == false) mountPI->startAnimation(); break; case ISD::Telescope::MOUNT_PARKED: mountStatus->setStyleSheet("font-weight:bold;background-color:red;border:2px solid black;"); if (mountPI->isAnimated()) mountPI->stopAnimation(); break; default: if (mountPI->isAnimated()) mountPI->stopAnimation(); } QJsonObject cStatus = { {"status", mountStatus->text()} }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::updateMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt) { raOUT->setText(ra); decOUT->setText(dec); azOUT->setText(az); altOUT->setText(alt); QJsonObject cStatus = { {"ra", dms::fromString(ra, false).Degrees()}, {"de", dms::fromString(dec, true).Degrees()}, {"az", dms::fromString(az, true).Degrees()}, {"at", dms::fromString(alt, true).Degrees()}, }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::updateCaptureStatus(Ekos::CaptureState status) { captureStatus->setText(Ekos::getCaptureStatusString(status)); captureProgress->setValue(captureProcess->getProgressPercentage()); overallCountDown.setHMS(0, 0, 0); overallCountDown = overallCountDown.addSecs(captureProcess->getOverallRemainingTime()); sequenceCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(captureProcess->getActiveJobRemainingTime()); if (status != Ekos::CAPTURE_ABORTED && status != Ekos::CAPTURE_COMPLETE && status != Ekos::CAPTURE_IDLE) { if (status == Ekos::CAPTURE_CAPTURING) capturePI->setColor(Qt::darkGreen); else capturePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (capturePI->isAnimated() == false) { capturePI->startAnimation(); countdownTimer.start(); } } else { if (capturePI->isAnimated()) { capturePI->stopAnimation(); countdownTimer.stop(); if (focusStatus->text() == "Complete") { if (focusPI->isAnimated()) focusPI->stopAnimation(); } imageProgress->setValue(0); sequenceLabel->setText(i18n("Sequence")); imageRemainingTime->setText("--:--:--"); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); } } QJsonObject cStatus = { {"status", captureStatus->text()}, {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(cStatus); } void Manager::updateCaptureProgress(Ekos::SequenceJob * job) { // Image is set to nullptr only on initial capture start up int completed = job->getCompleted(); // if (job->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) // completed = job->getCompleted() + 1; // else // completed = job->isPreview() ? job->getCompleted() : job->getCompleted() + 1; if (job->isPreview() == false) { sequenceLabel->setText(QString("Job # %1/%2 %3 (%4/%5)") .arg(captureProcess->getActiveJobID() + 1) .arg(captureProcess->getJobCount()) .arg(job->getFullPrefix()) .arg(completed) .arg(job->getCount())); } else sequenceLabel->setText(i18n("Preview")); sequenceProgress->setRange(0, job->getCount()); sequenceProgress->setValue(completed); QJsonObject status = { {"seqv", completed}, {"seqr", job->getCount()}, {"seql", sequenceLabel->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); if (job->getStatus() == SequenceJob::JOB_BUSY) { QString uuid = QUuid::createUuid().toString(); uuid = uuid.remove(QRegularExpression("[-{}]")); // FITSView *image = job->getActiveChip()->getImageView(FITS_NORMAL); // ekosLiveClient.get()->media()->sendPreviewImage(image, uuid); // ekosLiveClient.get()->cloud()->sendPreviewImage(image, uuid); QString filename = job->property("filename").toString(); ekosLiveClient.get()->media()->sendPreviewImage(filename, uuid); if (job->isPreview() == false) ekosLiveClient.get()->cloud()->sendPreviewImage(filename, uuid); } } void Manager::updateExposureProgress(Ekos::SequenceJob * job) { imageCountDown.setHMS(0, 0, 0); imageCountDown = imageCountDown.addSecs(job->getExposeLeft()); if (imageCountDown.hour() == 23) imageCountDown.setHMS(0, 0, 0); imageProgress->setRange(0, job->getExposure()); imageProgress->setValue(job->getExposeLeft()); imageRemainingTime->setText(imageCountDown.toString("hh:mm:ss")); QJsonObject status { {"expv", job->getExposeLeft()}, {"expr", job->getExposure()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void Manager::updateCaptureCountDown() { overallCountDown = overallCountDown.addSecs(-1); if (overallCountDown.hour() == 23) overallCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(-1); if (sequenceCountDown.hour() == 23) sequenceCountDown.setHMS(0, 0, 0); overallRemainingTime->setText(overallCountDown.toString("hh:mm:ss")); sequenceRemainingTime->setText(sequenceCountDown.toString("hh:mm:ss")); QJsonObject status = { {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void Manager::updateFocusStarPixmap(QPixmap &starPixmap) { if (starPixmap.isNull()) return; focusStarPixmap.reset(new QPixmap(starPixmap)); focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::updateFocusProfilePixmap(QPixmap &profilePixmap) { if (profilePixmap.isNull()) return; focusProfileImage->setPixmap(profilePixmap); } void Manager::setFocusStatus(Ekos::FocusState status) { focusStatus->setText(Ekos::getFocusStatusString(status)); if (status >= Ekos::FOCUS_PROGRESS) { focusPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else if (status == Ekos::FOCUS_COMPLETE && Options::enforceAutofocus() && captureProcess->getActiveJobID() != -1) { focusPI->setColor(Qt::darkGreen); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else { if (focusPI->isAnimated()) focusPI->stopAnimation(); } QJsonObject cStatus = { {"status", focusStatus->text()} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void Manager::updateGuideStatus(Ekos::GuideState status) { guideStatus->setText(Ekos::getGuideStatusString(status)); switch (status) { case Ekos::GUIDE_IDLE: case Ekos::GUIDE_CALIBRATION_ERROR: case Ekos::GUIDE_ABORTED: case Ekos::GUIDE_SUSPENDED: case Ekos::GUIDE_DITHERING_ERROR: case Ekos::GUIDE_CALIBRATION_SUCESS: if (guidePI->isAnimated()) guidePI->stopAnimation(); break; case Ekos::GUIDE_CALIBRATING: guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_GUIDING: guidePI->setColor(Qt::darkGreen); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_DITHERING: guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_DITHERING_SUCCESS: guidePI->setColor(Qt::darkGreen); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; default: if (guidePI->isAnimated()) guidePI->stopAnimation(); break; } QJsonObject cStatus = { {"status", guideStatus->text()} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void Manager::updateGuideStarPixmap(QPixmap &starPix) { if (starPix.isNull()) return; guideStarPixmap.reset(new QPixmap(starPix)); guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::updateGuideProfilePixmap(QPixmap &profilePix) { if (profilePix.isNull()) return; guideProfileImage->setPixmap(profilePix); } void Manager::setTarget(SkyObject * o) { mountTarget->setText(o->name()); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", o->name()}})); } void Manager::showEkosOptions() { QWidget * currentWidget = toolsWidget->currentWidget(); if (alignProcess.get() && alignProcess.get() == currentWidget) { KConfigDialog * alignSettings = KConfigDialog::exists("alignsettings"); if (alignSettings) { alignSettings->setEnabled(true); alignSettings->show(); } return; } if (guideProcess.get() && guideProcess.get() == currentWidget) { KConfigDialog::showDialog("guidesettings"); return; } if (ekosOptionsWidget == nullptr) { optionsB->click(); } else if (KConfigDialog::showDialog("settings")) { KConfigDialog * cDialog = KConfigDialog::exists("settings"); cDialog->setCurrentPage(ekosOptionsWidget); } } void Manager::getCurrentProfileTelescopeInfo(double &primaryFocalLength, double &primaryAperture, double &guideFocalLength, double &guideAperture) { ProfileInfo * pi = getCurrentProfile(); if (pi) { int primaryScopeID = 0, guideScopeID = 0; primaryScopeID = pi->primaryscope; guideScopeID = pi->guidescope; if (primaryScopeID > 0 || guideScopeID > 0) { // Get all OAL equipment filter list QList m_scopeList; KStarsData::Instance()->userdb()->GetAllScopes(m_scopeList); foreach(OAL::Scope * oneScope, m_scopeList) { if (oneScope->id().toInt() == primaryScopeID) { primaryFocalLength = oneScope->focalLength(); primaryAperture = oneScope->aperture(); } if (oneScope->id().toInt() == guideScopeID) { guideFocalLength = oneScope->focalLength(); guideAperture = oneScope->aperture(); } } } } } void Manager::updateDebugInterfaces() { KSUtils::Logging::SyncFilterRules(); for (ISD::GDInterface * device : genericDevices) { INDI::Property * debugProp = device->getProperty("DEBUG"); ISwitchVectorProperty * debugSP = nullptr; if (debugProp) debugSP = debugProp->getSwitch(); else continue; // Check if the debug interface matches the driver device class if ( ( opsLogs->getINDIDebugInterface() & device->getBaseDevice()->getDriverInterface() ) && debugSP->sp[0].s != ISS_ON) { debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Enabling debug logging for %1...", device->getDeviceName())); } else if ( !( opsLogs->getINDIDebugInterface() & device->getBaseDevice()->getDriverInterface() ) && debugSP->sp[0].s != ISS_OFF) { debugSP->sp[0].s = ISS_OFF; debugSP->sp[1].s = ISS_ON; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Disabling debug logging for %1...", device->getDeviceName())); } if (opsLogs->isINDISettingsChanged()) device->setConfig(SAVE_CONFIG); } } void Manager::watchDebugProperty(ISwitchVectorProperty * svp) { if (!strcmp(svp->name, "DEBUG")) { ISD::GenericDevice * deviceInterface = qobject_cast(sender()); // We don't process pure general interfaces if (deviceInterface->getBaseDevice()->getDriverInterface() == INDI::BaseDevice::GENERAL_INTERFACE) return; // If debug was turned off, but our logging policy requires it then turn it back on. // We turn on debug logging if AT LEAST one driver interface is selected by the logging settings if (svp->s == IPS_OK && svp->sp[0].s == ISS_OFF && (opsLogs->getINDIDebugInterface() & deviceInterface->getBaseDevice()->getDriverInterface())) { svp->sp[0].s = ISS_ON; svp->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-enabling debug logging for %1...", deviceInterface->getDeviceName())); } // To turn off debug logging, NONE of the driver interfaces should be enabled in logging settings. // For example, if we have CCD+FilterWheel device and CCD + Filter Wheel logging was turned on in // the log settings, then if the user turns off only CCD logging, the debug logging is NOT // turned off until he turns off Filter Wheel logging as well. else if (svp->s == IPS_OK && svp->sp[0].s == ISS_ON && !(opsLogs->getINDIDebugInterface() & deviceInterface->getBaseDevice()->getDriverInterface())) { svp->sp[0].s = ISS_OFF; svp->sp[1].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-disabling debug logging for %1...", deviceInterface->getDeviceName())); } } } void Manager::announceEvent(const QString &message, KSNotification::EventType event) { ekosLiveClient.get()->message()->sendEvent(message, event); } void Manager::connectModules() { // Guide <---> Capture connections if (captureProcess.get() && guideProcess.get()) { captureProcess.get()->disconnect(guideProcess.get()); guideProcess.get()->disconnect(captureProcess.get()); // Guide Limits connect(guideProcess.get(), &Ekos::Guide::newStatus, captureProcess.get(), &Ekos::Capture::setGuideStatus, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::newAxisDelta, captureProcess.get(), &Ekos::Capture::setGuideDeviation); // Dithering connect(captureProcess.get(), &Ekos::Capture::newStatus, guideProcess.get(), &Ekos::Guide::setCaptureStatus, Qt::UniqueConnection); // Guide Head connect(captureProcess.get(), &Ekos::Capture::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::guideChipUpdated, captureProcess.get(), &Ekos::Capture::setGuideChip, Qt::UniqueConnection); // Meridian Flip connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, guideProcess.get(), &Ekos::Guide::abort, Qt::UniqueConnection); - connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, guideProcess.get(), [&]() - { - if (Options::resetGuideCalibration()) - guideProcess->clearCalibration(); - guideProcess->guide(); - }); + connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, guideProcess.get(), &Ekos::Guide::guideAfterMeridianFlip, Qt::UniqueConnection); } // Guide <---> Mount connections if (guideProcess.get() && mountProcess.get()) { // Parking connect(mountProcess.get(), &Ekos::Mount::newStatus, guideProcess.get(), &Ekos::Guide::setMountStatus, Qt::UniqueConnection); } // Focus <---> Guide connections if (guideProcess.get() && focusProcess.get()) { // Suspend connect(focusProcess.get(), &Ekos::Focus::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); } // Capture <---> Focus connections if (captureProcess.get() && focusProcess.get()) { // Check focus HFR value connect(captureProcess.get(), &Ekos::Capture::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); // Reset Focus connect(captureProcess.get(), &Ekos::Capture::resetFocus, focusProcess.get(), &Ekos::Focus::resetFrame, Qt::UniqueConnection); // New Focus Status connect(focusProcess.get(), &Ekos::Focus::newStatus, captureProcess.get(), &Ekos::Capture::setFocusStatus, Qt::UniqueConnection); // New Focus HFR connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); } // Capture <---> Align connections if (captureProcess.get() && alignProcess.get()) { // Alignment flag connect(alignProcess.get(), &Ekos::Align::newStatus, captureProcess.get(), &Ekos::Capture::setAlignStatus, Qt::UniqueConnection); // Solver data connect(alignProcess.get(), &Ekos::Align::newSolverResults, captureProcess.get(), &Ekos::Capture::setAlignResults, Qt::UniqueConnection); // Capture Status connect(captureProcess.get(), &Ekos::Capture::newStatus, alignProcess.get(), &Ekos::Align::setCaptureStatus, Qt::UniqueConnection); } // Capture <---> Mount connections if (captureProcess.get() && mountProcess.get()) { // Meridian Flip states connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, mountProcess.get(), &Ekos::Mount::disableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, mountProcess.get(), &Ekos::Mount::enableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::newMeridianFlipStatus, mountProcess.get(), &Ekos::Mount::meridianFlipStatusChanged, Qt::UniqueConnection); connect(mountProcess.get(), &Ekos::Mount::newMeridianFlipStatus, captureProcess.get(), &Ekos::Capture::meridianFlipStatusChanged, Qt::UniqueConnection); // Mount Status connect(mountProcess.get(), &Ekos::Mount::newStatus, captureProcess.get(), &Ekos::Capture::setMountStatus, Qt::UniqueConnection); } // Capture <---> EkosLive connections if (captureProcess.get() && ekosLiveClient.get()) { captureProcess.get()->disconnect(ekosLiveClient.get()->message()); connect(captureProcess.get(), &Ekos::Capture::dslrInfoRequested, ekosLiveClient.get()->message(), &EkosLive::Message::requestDSLRInfo); connect(captureProcess.get(), &Ekos::Capture::sequenceChanged, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSequence); connect(captureProcess.get(), &Ekos::Capture::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSettings); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Focus <---> Mount connections if (focusProcess.get() && mountProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, focusProcess.get(), &Ekos::Focus::setMountStatus, Qt::UniqueConnection); } // Mount <---> Align connections if (mountProcess.get() && alignProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, alignProcess.get(), &Ekos::Align::setMountStatus, Qt::UniqueConnection); } // Mount <---> Guide connections if (mountProcess.get() && guideProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::pierSideChanged, guideProcess.get(), &Ekos::Guide::setPierSide, Qt::UniqueConnection); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Align <--> EkosLive connections if (alignProcess.get() && ekosLiveClient.get()) { alignProcess.get()->disconnect(ekosLiveClient.get()->message()); alignProcess.get()->disconnect(ekosLiveClient.get()->media()); connect(alignProcess.get(), &Ekos::Align::newStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignStatus); connect(alignProcess.get(), &Ekos::Align::newSolution, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignSolution); connect(alignProcess.get(), &Ekos::Align::newPAHStage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHStage); connect(alignProcess.get(), &Ekos::Align::newPAHMessage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHMessage); connect(alignProcess.get(), &Ekos::Align::PAHEnabled, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHEnabled); connect(alignProcess.get(), &Ekos::Align::newImage, [&](FITSView * view) { ekosLiveClient.get()->media()->sendPreviewImage(view, QString()); }); connect(alignProcess.get(), &Ekos::Align::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendUpdatedFrame); connect(alignProcess.get(), &Ekos::Align::polarResultUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::setPolarResults); connect(alignProcess.get(), &Ekos::Align::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendAlignSettings); connect(alignProcess.get(), &Ekos::Align::newCorrectionVector, ekosLiveClient.get()->media(), &EkosLive::Media::setCorrectionVector); } } void Manager::setEkosLiveConnected(bool enabled) { ekosLiveClient.get()->setConnected(enabled); } void Manager::setEkosLiveConfig(bool onlineService, bool rememberCredentials, bool autoConnect) { ekosLiveClient.get()->setConfig(onlineService, rememberCredentials, autoConnect); } void Manager::setEkosLiveUser(const QString &username, const QString &password) { ekosLiveClient.get()->setUser(username, password); } bool Manager::ekosLiveStatus() { return ekosLiveClient.get()->isConnected(); } void Manager::syncActiveDevices() { for (auto oneDevice : genericDevices) { uint32_t devInterface = oneDevice->getDriverInterface(); if (devInterface & (INDI::BaseDevice::TELESCOPE_INTERFACE | INDI::BaseDevice::DOME_INTERFACE | INDI::BaseDevice::GPS_INTERFACE | INDI::BaseDevice::FILTER_INTERFACE)) { // #1 Make sure all PREVIOUSLY defined drivers // are properly updated. #if 0 for (auto otherDevice : genericDevices) { if (otherDevice == oneDevice) continue; ITextVectorProperty *tvp = otherDevice->getBaseDevice()->getText("ACTIVE_DEVICES"); if (tvp) { IText *snoopProperty = nullptr; if (devInterface & INDI::BaseDevice::TELESCOPE_INTERFACE) snoopProperty = IUFindText(tvp, "ACTIVE_TELESCOPE"); else if (devInterface & INDI::BaseDevice::DOME_INTERFACE) snoopProperty = IUFindText(tvp, "ACTIVE_DOME"); else if (devInterface & INDI::BaseDevice::GPS_INTERFACE) snoopProperty = IUFindText(tvp, "ACTIVE_GPS"); else if (devInterface & INDI::BaseDevice::FILTER_INTERFACE) snoopProperty = IUFindText(tvp, "ACTIVE_FILTER"); if (snoopProperty && strcmp(snoopProperty->text, oneDevice->getDeviceName())) { IUSaveText(snoopProperty, oneDevice->getDeviceName()); otherDevice->getDriverInfo()->getClientManager()->sendNewText(tvp); } } } #endif // #2 Make sure CURRENT driver is updated ITextVectorProperty *tvp = oneDevice->getBaseDevice()->getText("ACTIVE_DEVICES"); if (tvp) { for (int i = 0; i < tvp->ntp; i++) { QList devs; if (!strcmp(tvp->tp[i].name, "ACTIVE_TELESCOPE")) { devs = findDevicesByInterface(INDI::BaseDevice::TELESCOPE_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_DOME")) { devs = findDevicesByInterface(INDI::BaseDevice::DOME_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_GPS")) { devs = findDevicesByInterface(INDI::BaseDevice::GPS_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_FILTER")) { devs = findDevicesByInterface(INDI::BaseDevice::FILTER_INTERFACE); } else if (!strcmp(tvp->tp[i].name, "ACTIVE_WEATHER")) { devs = findDevicesByInterface(INDI::BaseDevice::WEATHER_INTERFACE); } if (!devs.empty()) { if (strcmp(tvp->tp[i].text, devs.first()->getDeviceName())) { IUSaveText(&tvp->tp[i], devs.first()->getDeviceName()); oneDevice->getDriverInfo()->getClientManager()->sendNewText(tvp); } } } } } } } }