diff --git a/kstars/ekos/guide/externalguide/phd2.cpp b/kstars/ekos/guide/externalguide/phd2.cpp index 0233b63a2..bdc820039 100644 --- a/kstars/ekos/guide/externalguide/phd2.cpp +++ b/kstars/ekos/guide/externalguide/phd2.cpp @@ -1,1271 +1,1331 @@ /* Ekos PHD2 Handler Copyright (C) 2016 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "phd2.h" #include "Options.h" #include "kspaths.h" #include "kstars.h" #include "ekos/manager.h" #include "fitsviewer/fitsdata.h" #include #include #include #include #include #include #include #define MAX_SET_CONNECTED_RETRIES 3 namespace Ekos { PHD2::PHD2() { tcpSocket = new QTcpSocket(this); connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readPHD2())); connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError))); //This list of available PHD Events is on https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring events["Version"] = Version; events["LockPositionSet"] = LockPositionSet; events["Calibrating"] = Calibrating; events["CalibrationComplete"] = CalibrationComplete; events["StarSelected"] = StarSelected; events["StartGuiding"] = StartGuiding; events["Paused"] = Paused; events["StartCalibration"] = StartCalibration; events["AppState"] = AppState; events["CalibrationFailed"] = CalibrationFailed; events["CalibrationDataFlipped"] = CalibrationDataFlipped; events["LoopingExposures"] = LoopingExposures; events["LoopingExposuresStopped"] = LoopingExposuresStopped; events["SettleBegin"] = SettleBegin; events["Settling"] = Settling; events["SettleDone"] = SettleDone; events["StarLost"] = StarLost; events["GuidingStopped"] = GuidingStopped; events["Resumed"] = Resumed; events["GuideStep"] = GuideStep; events["GuidingDithered"] = GuidingDithered; events["LockPositionLost"] = LockPositionLost; events["Alert"] = Alert; events["GuideParamChange"] = GuideParamChange; //This list of available PHD Methods is on https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring //Only some of the methods are implemented. The ones that say COMMAND_RECEIVED simply return a 0 saying the command was received. - //capture_single_frame + methodResults["capture_single_frame"] = CAPTURE_SINGLE_FRAME; methodResults["clear_calibration"] = CLEAR_CALIBRATION_COMMAND_RECEIVED; methodResults["dither"] = DITHER_COMMAND_RECEIVED; //find_star //flip_calibration //get_algo_param_names //get_algo_param //get_app_state //get_calibrated //get_calibration_data methodResults["get_connected"] = IS_EQUIPMENT_CONNECTED; //get_cooler_status - //get_current_equipment + methodResults["get_current_equipment"] = GET_CURRENT_EQUIPMENT; methodResults["get_dec_guide_mode"] = DEC_GUIDE_MODE; methodResults["get_exposure"] = EXPOSURE_TIME; methodResults["get_exposure_durations"] = EXPOSURE_DURATIONS; - //get_lock_position + methodResults["get_lock_position"] = LOCK_POSITION; //get_lock_shift_enabled //get_lock_shift_params //get_paused methodResults["get_pixel_scale"] = PIXEL_SCALE; //get_profile //get_profiles //get_search_region //get_sensor_temperature methodResults["get_star_image"] = STAR_IMAGE; //get_use_subframes methodResults["guide"] = GUIDE_COMMAND_RECEIVED; //guide_pulse - //loop + methodResults["loop"] = LOOP; //save_image //set_algo_param methodResults["set_connected"] = CONNECTION_RESULT; methodResults["set_dec_guide_mode"] = SET_DEC_GUIDE_MODE_COMMAND_RECEIVED; methodResults["set_exposure"] = SET_EXPOSURE_COMMAND_RECEIVED; - //set_lock_position + methodResults["set_lock_position"] = SET_LOCK_POSITION; //set_lock_shift_enabled //set_lock_shift_params methodResults["set_paused"] = SET_PAUSED_COMMAND_RECEIVED; //set_profile //shutdown methodResults["stop_capture"] = STOP_CAPTURE_COMMAND_RECEIVED; QDir writableDir; writableDir.mkdir(KSPaths::writableLocation(QStandardPaths::TempLocation)); abortTimer = new QTimer(this); connect(abortTimer, &QTimer::timeout, this, [ = ] { qCDebug(KSTARS_EKOS_GUIDE) << "Lost Star timeout expired"; abort(); }); ditherTimer = new QTimer(this); connect(ditherTimer, &QTimer::timeout, this, [ = ] { qCDebug(KSTARS_EKOS_GUIDE) << "ditherTimer expired, state" << state << "dithering" << isDitherActive << "settling" << isSettling; ditherTimer->stop(); isDitherActive = false; isSettling = false; if (Options::ditherFailAbortsAutoGuide()) { abort(); emit newStatus(GUIDE_DITHERING_ERROR); } else { emit newLog(i18n("PHD2: There was no dithering response from PHD2, but continue guiding.")); emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); } }); } PHD2::~PHD2() { delete abortTimer; delete ditherTimer; } bool PHD2::Connect() { if (connection == DISCONNECTED) { emit newLog(i18n("Connecting to PHD2 Host: %1, on port %2. . .", Options::pHD2Host(), Options::pHD2Port())); tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port()); } else // Already connected, let's connect equipment connectEquipment(true); return true; } void PHD2::ResetConnectionState() { connection = DISCONNECTED; // clear the outstanding and queued RPC requests pendingRpcResultType = NO_RESULT; rpcRequestQueue.clear(); starImageRequested = false; isSettling = false; isDitherActive = false; ditherTimer->stop(); abortTimer->stop(); } bool PHD2::Disconnect() { if (connection == EQUIPMENT_CONNECTED) { // !FIXME-ag this is not reliable because we are just sending // a blind request to disconnect equipment then close the // socket. PHD2 will refuse to disconnect the equipment if it // is still looping exposures // // to be reliable it should: // call stop_capture // wait for the stop to complete by polling get_app_state // disconnect equipment // disconnect from phd2 connectEquipment(false); } ResetConnectionState(); tcpSocket->disconnectFromHost(); emit newStatus(GUIDE_DISCONNECTED); return true; } void PHD2::displayError(QAbstractSocket::SocketError socketError) { switch (socketError) { case QAbstractSocket::RemoteHostClosedError: break; case QAbstractSocket::HostNotFoundError: emit newLog(i18n("The host was not found. Please check the host name and port settings in Guide options.")); emit newStatus(GUIDE_DISCONNECTED); break; case QAbstractSocket::ConnectionRefusedError: emit newLog(i18n("The connection was refused by the peer. Make sure the PHD2 is running, and check that " "the host name and port settings are correct.")); emit newStatus(GUIDE_DISCONNECTED); break; default: emit newLog(i18n("The following error occurred: %1.", tcpSocket->errorString())); } ResetConnectionState(); } void PHD2::readPHD2() { while (!tcpSocket->atEnd() && tcpSocket->canReadLine()) { QByteArray line = tcpSocket->readLine(); if (line.isEmpty()) continue; QJsonParseError qjsonError; QJsonDocument jdoc = QJsonDocument::fromJson(line, &qjsonError); if (qjsonError.error != QJsonParseError::NoError) { emit newLog(i18n("PHD2: invalid response received: %1", QString(line))); emit newLog(i18n("PHD2: JSON error: %1", qjsonError.errorString())); continue; } QJsonObject jsonObj = jdoc.object(); if (jsonObj.contains("Event")) processPHD2Event(jsonObj, line); else if (jsonObj.contains("error")) processPHD2Error(jsonObj, line); else if (jsonObj.contains("result")) processPHD2Result(jsonObj, line); } } void PHD2::processPHD2Event(const QJsonObject &jsonEvent, const QByteArray &line) { if (Options::verboseLogging()) qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: event:" << line; QString eventName = jsonEvent["Event"].toString(); if (!events.contains(eventName)) { emit newLog(i18n("Unknown PHD2 event: %1", eventName)); return; } event = events.value(eventName); switch (event) { case Version: emit newLog(i18n("PHD2: Version %1", jsonEvent["PHDVersion"].toString())); connection = CONNECTED; break; case CalibrationComplete: setEquipmentConnected(); emit newLog(i18n("PHD2: Calibration Complete.")); emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); break; case StartGuiding: setEquipmentConnected(); updateGuideParameters(); + requestCurrentEquipmentUpdate(); // Do not report guiding as started because it will start scheduled capture before guiding is settled // just print the log message and GUIDE_STARTED status will be set in SettleDone // phd2 will always send SettleDone event emit newLog(i18n("PHD2: Waiting for guiding to settle.")); break; case Paused: state = PAUSED; emit newLog(i18n("PHD2: Guiding Paused.")); emit newStatus(Ekos::GUIDE_SUSPENDED); break; case StartCalibration: state = CALIBRATING; emit newLog(i18n("PHD2: Calibration Started.")); emit newStatus(Ekos::GUIDE_CALIBRATING); break; case AppState: // AppState is the last of the initial messages received when we first connect to PHD2 processPHD2State(jsonEvent["State"].toString()); // if the equipment is not already connected, then try to connect it. if (connection != EQUIPMENT_CONNECTED) connectEquipment(true); break; case CalibrationFailed: emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString())); emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); break; case CalibrationDataFlipped: emit newLog(i18n("Calibration Data Flipped.")); break; case LoopingExposures: state = LOOPING; //emit newLog(i18n("PHD2: Looping Exposures.")); break; case LoopingExposuresStopped: state = STOPPED; emit newLog(i18n("PHD2: Looping Exposures Stopped.")); break; case Calibrating: case Settling: case SettleBegin: //This can happen for guiding or for dithering. A Settle done event will arrive when it finishes. break; case SettleDone: { bool error = false; if (jsonEvent["Status"].toInt() != 0) { error = true; emit newLog(i18n("PHD2: Settling failed (%1).", jsonEvent["Error"].toString())); } bool wasDithering = isDitherActive; isDitherActive = false; isSettling = false; if (wasDithering) { ditherTimer->stop(); if (error && Options::ditherFailAbortsAutoGuide()) { abort(); emit newStatus(GUIDE_DITHERING_ERROR); } else { if (error) emit newLog(i18n("PHD2: There was a dithering error, but continue guiding.")); emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); } } else { if (error) { emit newLog(i18n("PHD2: Settling failed, aborted.")); emit newStatus(GUIDE_ABORTED); } else { // settle completed after "guide" command emit newLog(i18n("PHD2: Settling complete, Guiding Started.")); emit newStatus(GUIDE_GUIDING); } } } break; case StarSelected: emit newLog(i18n("PHD2: Star Selected.")); break; case StarLost: emit newLog(i18n("PHD2: Star Lost. Trying to reacquire.")); if (state != LOSTLOCK) { state = LOSTLOCK; abortTimer->start(Options::guideLostStarTimeout() * 1000); qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout started (" << Options::guideLostStarTimeout() << " sec)"; } break; case GuidingStopped: state = STOPPED; emit newLog(i18n("PHD2: Guiding Stopped.")); emit newStatus(Ekos::GUIDE_ABORTED); break; case Resumed: emit newLog(i18n("PHD2: Guiding Resumed.")); emit newStatus(Ekos::GUIDE_GUIDING); break; case GuideStep: { if (state == LOSTLOCK) { emit newLog(i18n("PHD2: Star found, guiding resumed.")); abortTimer->stop(); state = GUIDING; } // JM 2018-08-05: GuideStep does not necessary mean we're guiding // It could be that we're settling. This needs to be double-checked. // else if (state != GUIDING) // { // emit newLog(i18n("PHD2: Guiding started up again.")); // emit newStatus(Ekos::GUIDE_GUIDING); // state = GUIDING; // } if (isDitherActive) return; double diff_ra_pixels, diff_de_pixels, diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec; QString RADirection, DECDirection; diff_ra_pixels = jsonEvent["RADistanceRaw"].toDouble(); diff_de_pixels = jsonEvent["DECDistanceRaw"].toDouble(); pulse_ra = jsonEvent["RADuration"].toDouble(); pulse_dec = jsonEvent["DECDuration"].toDouble(); RADirection = jsonEvent["RADirection"].toString(); DECDirection = jsonEvent["DECDirection"].toString(); if (RADirection == "East") pulse_ra = -pulse_ra; //West Direction is Positive, East is Negative if (DECDirection == "South") pulse_dec = -pulse_dec; //South Direction is Negative, North is Positive //If the pixelScale is properly set from PHD2, the second block of code is not needed, but if not, we will attempt to calculate the ra and dec error without it. if (pixelScale != 0) { diff_ra_arcsecs = diff_ra_pixels * pixelScale; diff_de_arcsecs = diff_de_pixels * pixelScale; } else { diff_ra_arcsecs = 206.26480624709 * diff_ra_pixels * ccdPixelSizeX / mountFocalLength; diff_de_arcsecs = 206.26480624709 * diff_de_pixels * ccdPixelSizeY / mountFocalLength; } if (std::isfinite(diff_ra_arcsecs) && std::isfinite(diff_de_arcsecs)) { errorLog.append(QPointF(diff_ra_arcsecs, diff_de_arcsecs)); if(errorLog.size() > 50) errorLog.remove(0); emit newAxisDelta(diff_ra_arcsecs, diff_de_arcsecs); emit newAxisPulse(pulse_ra, pulse_dec); double total_sqr_RA_error = 0.0; double total_sqr_DE_error = 0.0; for (auto &point : errorLog) { total_sqr_RA_error += point.x() * point.x(); total_sqr_DE_error += point.y() * point.y(); } emit newAxisSigma(sqrt(total_sqr_RA_error / errorLog.size()), sqrt(total_sqr_DE_error / errorLog.size())); } - - requestStarImage(32); //This requests a star image for the guide view. 32 x 32 pixels + //Note that if it is receiving full size remote images, it should not get the guide star image. + //But if it is not getting the full size images, or if the current camera is not in Ekos, it should get the guide star image + //If we are getting the full size image, we will want to know the lock position for the image that loads in the viewer. + if ( Options::guideSubframeEnabled() || currentCameraIsNotInEkos ) + requestStarImage(32); //This requests a star image for the guide view. 32 x 32 pixels + else + requestLockPosition(); } break; case GuidingDithered: break; case LockPositionSet: emit newLog(i18n("PHD2: Lock Position Set.")); break; case LockPositionLost: emit newLog(i18n("PHD2: Lock Position Lost.")); if (state == CALIBRATING) emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); break; case Alert: emit newLog(i18n("PHD2 %1: %2", jsonEvent["Type"].toString(), jsonEvent["Msg"].toString())); break; case GuideParamChange: //Don't do anything for now, might change this later. //Some Possible Parameter Names: //Backlash comp enabled, Backlash comp amount, //For Each Axis: MinMove, Max Duration, //PPEC aggressiveness, PPEC prediction weight, //Resist switch minimum motion, Resist switch aggression, //Low-pass minimum move, Low-pass slope weight, //Low-pass2 minimum move, Low-pass2 aggressiveness, //Hysteresis hysteresis, Hysteresis aggression break; } } void PHD2::processPHD2State(const QString &phd2State) { if (phd2State == "Stopped") state = STOPPED; else if (phd2State == "Selected") state = SELECTED; else if (phd2State == "Calibrating") state = CALIBRATING; else if (phd2State == "Guiding") state = GUIDING; else if (phd2State == "LostLock") state = LOSTLOCK; else if (phd2State == "Paused") state = PAUSED; else if (phd2State == "Looping") state = LOOPING; } void PHD2::processPHD2Result(const QJsonObject &jsonObj, const QByteArray &line) { PHD2ResultType resultType = takeRequestFromList(jsonObj); if (resultType == STAR_IMAGE) qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received star image response, id" << jsonObj["id"].toInt(); // don't spam the log with image data else qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: response:" << line; switch (resultType) { case NO_RESULT: //Ekos didn't ask for this result? break; - //capture_single_frame + case CAPTURE_SINGLE_FRAME: //capture_single_frame + break; + case CLEAR_CALIBRATION_COMMAND_RECEIVED: //clear_calibration break; case DITHER_COMMAND_RECEIVED: //dither emit newStatus(Ekos::GUIDE_DITHERING); break; //find_star //flip_calibration //get_algo_param_names //get_algo_param //get_app_state //get_calibrated //get_calibration_data case IS_EQUIPMENT_CONNECTED: //get_connected { bool isConnected = jsonObj["result"].toBool(); if(isConnected) { setEquipmentConnected(); } else { connection = EQUIPMENT_DISCONNECTED; emit newStatus(Ekos::GUIDE_DISCONNECTED); } } break; //get_cooler_status - //get_current_equipment + case GET_CURRENT_EQUIPMENT: //get_current_equipment + { + QJsonObject equipObject = jsonObj["result"].toObject(); + currentCamera = equipObject["camera"].toObject()["name"].toString(); + currentMount = equipObject["mount"].toObject()["name"].toString(); + currentAuxMount = equipObject["aux_mount"].toObject()["name"].toString(); + + emit guideEquipmentUpdated(); + + break; + } + case DEC_GUIDE_MODE: //get_dec_guide_mode { QString mode = jsonObj["result"].toString(); KStars::Instance()->ekosManager()->guideModule()->updateDirectionsFromPHD2(mode); emit newLog(i18n("PHD2: DEC Guide Mode is Set to: %1", mode)); } break; case EXPOSURE_TIME: //get_exposure { int exposurems = jsonObj["result"].toInt(); double exposureTime = exposurems / 1000.0; KStars::Instance()->ekosManager()->guideModule()->setExposure(exposureTime); emit newLog(i18n("PHD2: Exposure Time set to: ") + QString::number(exposureTime, 'f', 2)); break; } case EXPOSURE_DURATIONS: //get_exposure_durations { QVariantList exposureListArray = jsonObj["result"].toArray().toVariantList(); logValidExposureTimes = i18n("PHD2: Valid Exposure Times: Auto, "); QList values; for(int i = 1; i < exposureListArray.size(); i ++) //For some reason PHD2 has a negative exposure time of 1 at the start of the array? values << exposureListArray.at(i).toDouble() / 1000.0; //PHD2 reports in ms. logValidExposureTimes += KStars::Instance()->ekosManager()->guideModule()->setRecommendedExposureValues(values); emit newLog(logValidExposureTimes); break; } - //get_lock_position + case LOCK_POSITION: //get_lock_position + { + if(jsonObj["result"].toArray().count()==2) + { + double x = jsonObj["result"].toArray().at(0).toDouble(); + double y = jsonObj["result"].toArray().at(1).toDouble(); + QVector3D newStarCenter(x, y, 0); + emit newStarPosition(newStarCenter, true); + + //This is needed so that PHD2 sends the new star pixmap when + //remote images are enabled. + emit newStarPixmap(guideFrame->getTrackingBoxPixmap()); + } + break; + } //get_lock_shift_enabled //get_lock_shift_params //get_paused case PIXEL_SCALE: //get_pixel_scale pixelScale = jsonObj["result"].toDouble(); if (pixelScale == 0) emit newLog(i18n("PHD2: Please set CCD and telescope parameters in PHD2, Pixel Scale is invalid.")); else emit newLog(i18n("PHD2: Pixel Scale is %1 arcsec per pixel", QString::number(pixelScale, 'f', 2))); break; //get_profile //get_profiles //get_search_region //get_sensor_temperature case STAR_IMAGE: //get_star_image { starImageRequested = false; QJsonObject jsonResult = jsonObj["result"].toObject(); processStarImage(jsonResult); break; } //get_use_subframes case GUIDE_COMMAND_RECEIVED: //guide break; + //guide_pulse - //loop + + case LOOP: //loop + break; + //save_image //set_algo_param case CONNECTION_RESULT: //set_connected checkIfEquipmentConnected(); break; case SET_DEC_GUIDE_MODE_COMMAND_RECEIVED: //set_dec_guide_mode checkDEGuideMode(); break; case SET_EXPOSURE_COMMAND_RECEIVED: //set_exposure requestExposureTime(); //This will check what it was set to and print a message as to what it is. break; - //set_lock_position + case SET_LOCK_POSITION: //set_lock_position + break; + //set_lock_shift_enabled //set_lock_shift_params case SET_PAUSED_COMMAND_RECEIVED: //set_paused break; //set_profile //shutdown case STOP_CAPTURE_COMMAND_RECEIVED: //stop_capture emit newStatus(GUIDE_ABORTED); break; } // send the next pending call sendNextRpcCall(); } void PHD2::processPHD2Error(const QJsonObject &jsonError, const QByteArray &line) { qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: error:" << line; QJsonObject jsonErrorObject = jsonError["error"].toObject(); emit newLog(i18n("PHD2 Error: %1", jsonErrorObject["message"].toString())); PHD2ResultType resultType = takeRequestFromList(jsonError); // This means the user mistakenly entered an invalid exposure time. if (resultType == SET_EXPOSURE_COMMAND_RECEIVED) { emit newLog(logValidExposureTimes); //This will let the user know the valid exposure durations QTimer::singleShot(300, [ = ] {requestExposureTime();}); //This will reset the Exposure time in Ekos to PHD2's current exposure time after a third of a second. } else if (resultType == CONNECTION_RESULT) { connection = EQUIPMENT_DISCONNECTED; emit newStatus(Ekos::GUIDE_DISCONNECTED); } else if (resultType == DITHER_COMMAND_RECEIVED) { ditherTimer->stop(); isSettling = false; isDitherActive = false; emit newStatus(GUIDE_DITHERING_ERROR); if (Options::ditherFailAbortsAutoGuide()) { abort(); emit newStatus(GUIDE_ABORTED); } else { // !FIXME-ag why is this trying to resume (un-pause)? resume(); } } else if (resultType == GUIDE_COMMAND_RECEIVED) { isSettling = false; } // send the next pending call sendNextRpcCall(); } //These methods process the Star Images the PHD2 provides void PHD2::setGuideView(FITSView *guideView) { guideFrame = guideView; } void PHD2::processStarImage(const QJsonObject &jsonStarFrame) { //The width and height of the received PHD2 Star Image int width = jsonStarFrame["width"].toInt(); int height = jsonStarFrame["height"].toInt(); QTemporaryFile tempfile(KSPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("phd2_XXXXXX")); tempfile.setAutoRemove(false); if (!tempfile.open()) { qCWarning(KSTARS_EKOS_GUIDE) << "could not create temp file for PHD2 star image"; return; } QString filename = tempfile.fileName(); //This section sets up the FITS File fitsfile *fptr = nullptr; int status = 0; long fpixel = 1, naxis = 2, nelements, exposure; long naxes[2] = { width, height }; char error_status[512] = {0}; if (fits_create_file(&fptr, QString('!' + filename).toLatin1().data(), &status)) { qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_file failed:" << error_status; return; } if (fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status)) { qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_img failed:" << error_status; status = 0; fits_close_file(fptr, &status); return; } //Note, this is made up. If you want the actual exposure time, you have to request it from PHD2 exposure = 1; fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status); //This section takes the Pixels from the JSON Document //Then it converts from base64 to a QByteArray //Then it creates a datastream from the QByteArray to the pixel array for the FITS File QByteArray converted = QByteArray::fromBase64(jsonStarFrame["pixels"].toString().toLocal8Bit()); //This finishes up and closes the FITS file nelements = naxes[0] * naxes[1]; if (fits_write_img(fptr, TUSHORT, fpixel, nelements, converted.data(), &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_EKOS_GUIDE) << "fits_write_img failed:" << error_status; status = 0; fits_close_file(fptr, &status); return; } if (fits_flush_file(fptr, &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_EKOS_GUIDE) << "fits_flush_file failed:" << error_status; status = 0; fits_close_file(fptr, &status); return; } if (fits_close_file(fptr, &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_EKOS_GUIDE) << "fits_close_file failed:" << error_status; return; } //This loads the FITS file in the Guide FITSView //Then it updates the Summary Screen auto conn = std::make_shared(); *conn = connect(guideFrame, &FITSView::loaded, [this, conn, width, height]() { // we'll take care of deleting the temp file //guideFrame->getImageData()->setAutoRemoveTemporaryFITS(false); guideFrame->updateFrame(); guideFrame->setTrackingBox(QRect(0, 0, width, height)); emit newStarPixmap(guideFrame->getTrackingBoxPixmap()); QObject::disconnect(*conn); }); guideFrame->loadFITS(filename, true); } void PHD2::setEquipmentConnected() { if (connection != EQUIPMENT_CONNECTED) { setConnectedRetries = 0; connection = EQUIPMENT_CONNECTED; emit newStatus(Ekos::GUIDE_CONNECTED); updateGuideParameters(); requestExposureDurations(); + requestCurrentEquipmentUpdate(); } } void PHD2::updateGuideParameters() { if (pixelScale == 0) requestPixelScale(); requestExposureTime(); checkDEGuideMode(); } //This section handles the methods/requests sent to PHD2, some are not implemented. //capture_single_frame +void PHD2::captureSingleFrame() +{ + sendPHD2Request("capture_single_frame"); +} //clear_calibration bool PHD2::clearCalibration() { if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } QJsonArray args; //This instructs PHD2 which calibration to clear. args << "mount"; sendPHD2Request("clear_calibration", args); return true; } //dither bool PHD2::dither(double pixels) { if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } if (isSettling) { qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring dither requested while already settling"; if (!isDitherActive) { // act like we just dithered so we get the appropriate // effects after the settling completes emit newStatus(Ekos::GUIDE_DITHERING); isDitherActive = true; } return true; } QJsonArray args; QJsonObject settle; int ditherTimeout = static_cast(Options::ditherTimeout()); settle.insert("pixels", static_cast(Options::ditherThreshold())); settle.insert("time", static_cast(Options::ditherSettle())); settle.insert("timeout", ditherTimeout); // Pixels args << pixels; // RA Only? args << false; // Settle args << settle; isSettling = true; isDitherActive = true; // PHD2 will send a SettleDone event shortly after the settling // timeout in PHD2. We don't really need a timer here, but we'll // set one anyway (belt and suspenders). Make sure to give an // extra time allowance since PHD2 won't report its timeout until // the completion of the next guide exposure after the timeout // period expires. enum { TIMEOUT_EXTRA_SECONDS = 60 }; // at least as long as any reasonable guide exposure int millis = (ditherTimeout + TIMEOUT_EXTRA_SECONDS) * 1000; ditherTimer->start(millis); sendPHD2Request("dither", args); emit newStatus(Ekos::GUIDE_DITHERING); return true; } //find_star //flip_calibration //get_algo_param_names //get_algo_param //get_app_state //get_calibrated //get_calibration_data //get_connected void PHD2::checkIfEquipmentConnected() { sendPHD2Request("get_connected"); } //get_cooler_status //get_current_equipment +void PHD2::requestCurrentEquipmentUpdate() +{ + sendPHD2Request("get_current_equipment"); +} //get_dec_guide_mode void PHD2::checkDEGuideMode() { sendPHD2Request("get_dec_guide_mode"); } //get_exposure void PHD2::requestExposureTime() { sendPHD2Request("get_exposure"); } //get_exposure_durations void PHD2::requestExposureDurations() { sendPHD2Request("get_exposure_durations"); } //get_lock_position +void PHD2::requestLockPosition() +{ + sendPHD2Request("get_lock_position"); +} //get_lock_shift_enabled //get_lock_shift_params //get_paused //get_pixel_scale void PHD2::requestPixelScale() { sendPHD2Request("get_pixel_scale"); } //get_profile //get_profiles //get_search_region //get_sensor_temperature //get_star_image void PHD2::requestStarImage(int size) { - // if (!Options::guideRemoteImagesEnabled()) - // return; - if (starImageRequested) { if (Options::verboseLogging()) qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: skip extra star image request"; return; } QJsonArray args2; args2 << size; // This is both the width and height. sendPHD2Request("get_star_image", args2); starImageRequested = true; } //get_use_subframes //guide bool PHD2::guide() { if (state == GUIDING) { emit newLog(i18n("PHD2: Guiding is already running.")); emit newStatus(Ekos::GUIDE_GUIDING); return true; } if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } QJsonArray args; QJsonObject settle; settle.insert("pixels", static_cast(Options::ditherThreshold())); settle.insert("time", static_cast(Options::ditherSettle())); settle.insert("timeout", static_cast(Options::ditherTimeout())); // Settle param args << settle; // Recalibrate param args << false; errorLog.clear(); isSettling = true; sendPHD2Request("guide", args); return true; } //guide_pulse //loop +void PHD2::loop() +{ + sendPHD2Request("loop"); +} //save_image //set_algo_param //set_connected void PHD2::connectEquipment(bool enable) { if ((connection == EQUIPMENT_CONNECTED && enable == true) || (connection == EQUIPMENT_DISCONNECTED && enable == false)) return; if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES) { setConnectedRetries = 0; connection = EQUIPMENT_DISCONNECTED; emit newStatus(Ekos::GUIDE_DISCONNECTED); return; } pixelScale = 0 ; QJsonArray args; // connected = enable args << enable; if (enable) emit newLog(i18n("PHD2: Connecting Equipment. . .")); else emit newLog(i18n("PHD2: Disconnecting Equipment. . .")); sendPHD2Request("set_connected", args); } //set_dec_guide_mode void PHD2::requestSetDEGuideMode(bool deEnabled, bool nEnabled, bool sEnabled) //Possible Settings Off, Auto, North, and South { QJsonArray args; if(deEnabled) { if(nEnabled && sEnabled) args << "Auto"; else if(nEnabled) args << "North"; else if(sEnabled) args << "South"; else args << "Off"; } else { args << "Off"; } sendPHD2Request("set_dec_guide_mode", args); } //set_exposure void PHD2::requestSetExposureTime(int time) //Note: time is in milliseconds { QJsonArray args; args << time; sendPHD2Request("set_exposure", args); } //set_lock_position +void PHD2::setLockPosition(double x,double y) +{ + // Note: false will mean if a guide star is near the coordinates, it will use that. + QJsonArray args; + args << x << y << false; + sendPHD2Request("set_lock_position", args); +} //set_lock_shift_enabled //set_lock_shift_params //set_paused bool PHD2::suspend() { if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } QJsonArray args; // Paused param args << true; // FULL param args << "full"; sendPHD2Request("set_paused", args); if (abortTimer->isActive()) { // Avoid that the a preceding lost star event leads to an abort while guiding is suspended. qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout cancelled."; abortTimer->stop(); } return true; } //set_paused (also) bool PHD2::resume() { if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } QJsonArray args; // Paused param args << false; sendPHD2Request("set_paused", args); if (state == LOSTLOCK) { qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout restarted."; abortTimer->start(Options::guideLostStarTimeout() * 1000); } return true; } //set_profile //shutdown //stop_capture bool PHD2::abort() { if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } abortTimer->stop(); sendPHD2Request("stop_capture"); return true; } //This method is not handled by PHD2 bool PHD2::calibrate() { // We don't explicitly do calibration since it is done in the guide step by PHD2 anyway //emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); return true; } //This is how information requests and commands for PHD2 are handled void PHD2::sendRpcCall(QJsonObject &call, PHD2ResultType resultType) { assert(resultType != NO_RESULT); // should be a real request assert(pendingRpcResultType == NO_RESULT); // only one pending RPC call at a time int rpcId = nextRpcId++; call.insert("id", rpcId); pendingRpcId = rpcId; pendingRpcResultType = resultType; QByteArray request = QJsonDocument(call).toJson(QJsonDocument::Compact); qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: request:" << request; request.append("\r\n"); qint64 n = tcpSocket->write(request); if ((int) n != request.size()) { qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: unexpected short write:" << n << "bytes of" << request.size(); } } void PHD2::sendNextRpcCall() { if (pendingRpcResultType != NO_RESULT) return; // a request is currently outstanding if (rpcRequestQueue.empty()) return; // no queued requests RpcCall &call = rpcRequestQueue.front(); sendRpcCall(call.call, call.resultType); rpcRequestQueue.pop_front(); } void PHD2::sendPHD2Request(const QString &method, const QJsonArray &args) { assert(methodResults.contains(method)); PHD2ResultType resultType = methodResults[method]; QJsonObject jsonRPC; jsonRPC.insert("jsonrpc", "2.0"); jsonRPC.insert("method", method); if (!args.empty()) jsonRPC.insert("params", args); if (pendingRpcResultType == NO_RESULT) { // no outstanding rpc call, send it right off sendRpcCall(jsonRPC, resultType); } else { // there is already an outstanding call, enqueue this call // until the prior call completes if (Options::verboseLogging()) qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: defer call" << method; rpcRequestQueue.push_back(RpcCall(jsonRPC, resultType)); } } PHD2::PHD2ResultType PHD2::takeRequestFromList(const QJsonObject &response) { if (Q_UNLIKELY(!response.contains("id"))) { qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with no id"; return NO_RESULT; } int id = response["id"].toInt(); if (Q_UNLIKELY(id != pendingRpcId)) { // RPC id mismatch -- this should never happen, something is // seriously wrong qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with id" << id; return NO_RESULT; } PHD2ResultType val = pendingRpcResultType; pendingRpcResultType = NO_RESULT; return val; } } diff --git a/kstars/ekos/guide/externalguide/phd2.h b/kstars/ekos/guide/externalguide/phd2.h index 089df3abd..06ee470fc 100644 --- a/kstars/ekos/guide/externalguide/phd2.h +++ b/kstars/ekos/guide/externalguide/phd2.h @@ -1,258 +1,271 @@ /* Ekos PHD2 Handler Copyright (C) 2016 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "../guideinterface.h" #include "fitsviewer/fitsview.h" #include #include #include #include #include class QTcpSocket; namespace Ekos { /** * @class PHD2 * Uses external PHD2 for guiding. * * @author Jasem Mutlaq * @version 1.1 */ class PHD2 : public GuideInterface { Q_OBJECT public: enum PHD2Event { Version, LockPositionSet, Calibrating, CalibrationComplete, StarSelected, StartGuiding, Paused, StartCalibration, AppState, CalibrationFailed, CalibrationDataFlipped, LoopingExposures, LoopingExposuresStopped, SettleBegin, Settling, SettleDone, StarLost, GuidingStopped, Resumed, GuideStep, GuidingDithered, LockPositionLost, Alert, GuideParamChange }; enum PHD2State { // these are the states exposed by phd2 STOPPED, SELECTED, CALIBRATING, GUIDING, LOSTLOCK, PAUSED, LOOPING, }; enum PHD2Connection { DISCONNECTED, CONNECTED, EQUIPMENT_DISCONNECTED, EQUIPMENT_CONNECTED }; enum PHD2MessageType { PHD2_UNKNOWN, PHD2_RESULT, PHD2_EVENT, PHD2_ERROR, }; // These are the PHD2 Results and the commands they are associated with enum PHD2ResultType { NO_RESULT, - //capture_single_frame + CAPTURE_SINGLE_FRAME, //capture_single_frame CLEAR_CALIBRATION_COMMAND_RECEIVED, //clear_calibration DITHER_COMMAND_RECEIVED, //dither //find_star //flip_calibration //get_algo_param_names //get_algo_param //get_app_state //get_calibrated //get_calibration_data IS_EQUIPMENT_CONNECTED, //get_connected //get_cooler_status - //get_current_equipment + GET_CURRENT_EQUIPMENT, //get_current_equipment DEC_GUIDE_MODE, //get_dec_guide_mode EXPOSURE_TIME, //get_exposure EXPOSURE_DURATIONS, //get_exposure_durations - //get_lock_position + LOCK_POSITION, //get_lock_position //get_lock_shift_enabled //get_lock_shift_params //get_paused PIXEL_SCALE, //get_pixel_scale //get_profile //get_profiles //get_search_region //get_sensor_temperature STAR_IMAGE, //get_star_image //get_use_subframes GUIDE_COMMAND_RECEIVED, //guide //guide_pulse - //loop + LOOP, //loop //save_image //set_algo_param CONNECTION_RESULT, //set_connected SET_DEC_GUIDE_MODE_COMMAND_RECEIVED, //set_dec_guide_mode SET_EXPOSURE_COMMAND_RECEIVED, //set_exposure - //set_lock_position + SET_LOCK_POSITION, //set_lock_position //set_lock_shift_enabled //set_lock_shift_params SET_PAUSED_COMMAND_RECEIVED, //set_paused //set_profile //shutdown STOP_CAPTURE_COMMAND_RECEIVED //stop_capture }; PHD2(); ~PHD2(); //These are the connection methods to connect the external guide program PHD2 bool Connect() override; bool Disconnect() override; bool isConnected() override { return (connection == CONNECTED || connection == EQUIPMENT_CONNECTED); } //These are the PHD2 Methods. Only some are implemented in Ekos. - //capture_single_frame + void captureSingleFrame(); //capture_single_frame bool clearCalibration() override; //clear_calibration bool dither(double pixels) override; //dither //find_star //flip_calibration //get_algo_param_names //get_algo_param //get_app_state //get_calibrated //get_calibration_data void checkIfEquipmentConnected(); //get_connected //get_cooler_status - //get_current_equipment + void requestCurrentEquipmentUpdate(); //get_current_equipment void checkDEGuideMode(); //get_dec_guide_mode void requestExposureTime(); //get_exposure void requestExposureDurations(); //get_exposure_durations - //get_lock_position + void requestLockPosition(); //get_lock_position //get_lock_shift_enabled //get_lock_shift_params //get_paused void requestPixelScale(); //get_pixel_scale //get_profile //get_profiles //get_search_region //get_sensor_temperature void requestStarImage(int size); //get_star_image //get_use_subframes bool guide() override; //guide //guide_pulse - //loop + void loop(); //loop //save_image //set_algo_param void connectEquipment(bool enable);//set_connected void requestSetDEGuideMode(bool deEnabled, bool nEnabled, bool sEnabled); //set_dec_guide_mode void requestSetExposureTime(int time); //set_exposure - //set_lock_position + void setLockPosition(double x, double y); //set_lock_position //set_lock_shift_enabled //set_lock_shift_params bool suspend() override; //set_paused bool resume() override; //set_paused //set_profile //shutdown bool abort() override; //stop_capture bool calibrate() override; //Note PHD2 does not have a separate calibrate command. This is unused. void setGuideView(FITSView *guideView); + QString getCurrentCamera(){ return currentCamera; } + QString getCurrentMount(){ return currentMount; } + QString getCurrentAuxMount(){ return currentAuxMount; } + + bool isCurrentCameraNotInEkos(){ return currentCameraIsNotInEkos; } + void setCurrentCameraIsNotInEkos(bool enable) {currentCameraIsNotInEkos = enable;} + private slots: void readPHD2(); void displayError(QAbstractSocket::SocketError socketError); private: QPointer guideFrame; QVector errorLog; void sendPHD2Request(const QString &method, const QJsonArray &args = QJsonArray()); void sendRpcCall(QJsonObject &call, PHD2ResultType resultType); void sendNextRpcCall(); void processPHD2Event(const QJsonObject &jsonEvent, const QByteArray &rawResult); void processPHD2Result(const QJsonObject &jsonObj, const QByteArray &rawResult); void processStarImage(const QJsonObject &jsonStarFrame); void processPHD2State(const QString &phd2State); void processPHD2Error(const QJsonObject &jsonError, const QByteArray &rawResult); PHD2ResultType takeRequestFromList(const QJsonObject &response); QTcpSocket *tcpSocket { nullptr }; int nextRpcId { 1 }; QHash events; // maps event name to event type QHash methodResults; // maps method name to result type int pendingRpcId; // ID of outstanding RPC call PHD2ResultType pendingRpcResultType { NO_RESULT }; // result type of outstanding RPC call bool starImageRequested { false }; // true when there is an outstanding star image request struct RpcCall { QJsonObject call; PHD2ResultType resultType; RpcCall() = default; RpcCall(const QJsonObject &call_, PHD2ResultType resultType_) : call(call_), resultType(resultType_) { } }; QVector rpcRequestQueue; PHD2State state { STOPPED }; bool isDitherActive { false }; bool isSettling { false }; PHD2Connection connection { DISCONNECTED }; PHD2Event event { Alert }; uint8_t setConnectedRetries { 0 }; void setEquipmentConnected(); void updateGuideParameters(); void ResetConnectionState(); QTimer *abortTimer; QTimer *ditherTimer; double pixelScale = 0; QString logValidExposureTimes; + + QString currentCamera; + QString currentMount; + QString currentAuxMount; + bool currentCameraIsNotInEkos; + }; } diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp index b1d0f8047..174c72bc1 100644 --- a/kstars/ekos/guide/guide.cpp +++ b/kstars/ekos/guide/guide.cpp @@ -1,3529 +1,3686 @@ /* 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::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) + if(guiderType != GUIDE_INTERNAL) { - connect(ccd, &ISD::CCD::newBLOBManager, [ccd](INDI::Property * prop) + connect(ccd, &ISD::CCD::newBLOBManager, [ccd, this](INDI::Property * prop) { if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) - ccd->setBLOBEnabled(Options::guideRemoteImagesEnabled(), prop->getName()); + { + 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"); - return; } 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; + } - guiderCombo->addItem(ccd->getDeviceName()); + //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; + } + } - checkCCD(); + //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; - binningCombo->setEnabled(targetChip->canBin()); + 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: - setBLOBEnabled(false); - break; case GUIDE_DISCONNECTED: - setBLOBEnabled(true); break; case GUIDE_CALIBRATING: case GUIDE_DITHERING: case GUIDE_STAR_SELECT: case GUIDE_CAPTURE: case GUIDE_GUIDING: case GUIDE_LOOPING: guider->abort(); break; default: break; } return true; } void Guide::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_INTERNAL) + if(guiderType != GUIDE_LINGUIDER) { captureB->setEnabled(true); loopB->setEnabled(true); - darkFrameCheck->setEnabled(true); - subFrameCheck->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); - captureB->setEnabled(false); - loopB->setEnabled(false); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); - setBLOBEnabled(false); + + 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); - setBLOBEnabled(true); + 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); - - if (Options::guideRemoteImagesEnabled() == false) - { - //guiderCombo->setCurrentIndex(-1); - guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); - } - else - guiderCombo->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); - if (Options::guideRemoteImagesEnabled() == false) - { - guiderCombo->setCurrentIndex(-1); - guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); - } - else - guiderCombo->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::setBLOBEnabled(bool enable, const QString &ccd) +void Guide::setExternalGuiderBLOBEnabled(bool enable) { - // Nothing to do if guider is international or remote images are enabled - if (guiderType == GUIDE_INTERNAL || Options::guideRemoteImagesEnabled()) + // Nothing to do if guider is internal + if (guiderType == GUIDE_INTERNAL) + return; + + if(!currentCCD) return; - // If guider is external and remote images option is disabled AND BLOB is enabled, then we disabled it + currentCCD->setBLOBEnabled(enable); - foreach(ISD::CCD *oneCCD, CCDs) + if(currentCCD->isBLOBEnabled()) { - // If it's not the desired CCD, continue. - if (ccd.isEmpty() == false && QString(oneCCD->getDeviceName()) != ccd) - continue; + if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) + useGuideHead = true; + else + useGuideHead = false; - if (enable == false && oneCCD->isBLOBEnabled()) - { - appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->setBLOBEnabled(enable); - } - // Re-enable BLOB reception if it was disabled before when using external guiders - else if (enable && oneCCD->isBLOBEnabled() == false) + ISD::CCDChip *targetChip = + currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (targetChip) { - appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->setBLOBEnabled(enable); + 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); - else if (ccd.isEmpty() == false) - { - QString ccdName = ccd; - ccdName = ccdName.remove(" Guider"); - setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); - } } 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); } - else if (index >= 0) - { - // Disable or enable selected CCD based on options - QString ccdName = guiderCombo->currentText().remove(" Guider"); - setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); - checkCCD(index); - } } ); FOVScopeCombo->setCurrentIndex(Options::guideScopeType()); connect(FOVScopeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTelescopeType); // Dark Frame Check connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); // Subframe check - connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); + 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); - capture(); + 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); - capture(); + 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, [&]() { - setBLOBEnabled(false); + //setExternalGuiderBLOBEnabled(false); guider->Connect(); }); connect(externalDisconnectB, &QPushButton::clicked, this, [&]() { - setBLOBEnabled(true); + //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 17431383f..94fd93661 100644 --- a/kstars/ekos/guide/guide.h +++ b/kstars/ekos/guide/guide.h @@ -1,634 +1,641 @@ /* 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); 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 setBLOBEnabled(bool enable, const QString &ccd = QString()); + 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/guide/guide.ui b/kstars/ekos/guide/guide.ui index 3d5d23a08..e581ebe58 100644 --- a/kstars/ekos/guide/guide.ui +++ b/kstars/ekos/guide/guide.ui @@ -1,1709 +1,1709 @@ Guide 0 0 - 736 + 782 568 3 3 3 3 3 3 Control 3 3 3 3 3 3 + + + + Automatically select the calibration star. + + + Auto Star + + + Capture + + + + false + + + Stop + + + 22 22 22 22 Show in FITS Viewer - - - - Loop - - - 22 22 22 22 Clear calibration data. .. 22 22 false 22 22 22 22 Manual Dither .. 22 22 - - - - false - - - Stop - - - - - - - <html><head/><body><p>Subframe the image around the guide star. Before checking this option, you must <span style=" font-weight:600;">first</span> capture an image and select a guide star. Uncheck it to take a full frame again.</p></body></html> - - - Subframe - - - <html><head/><body><p>Subtract dark frame. If no dark frame is available, a new dark frame shall be captured and saved for future use.</p></body></html> Dark - - - - Automatically select the calibration star. - + + - Auto Star + Loop false 0 0 Guide true true + + + + <html><head/><body><p>Subframe the image around the guide star. Or for PHD2, receive the Guide Star Image instead of the full image frame. For the Internal Guider, before checking this option, you must <span style=" font-weight:600;">first</span> capture an image and select a guide star. Uncheck it to take a full frame again.</p></body></html> + + + Subframe + + + 1 3 Exposure time in seconds Exp: Select which device receives the guiding correction commands. Via: Guide star tracking box size. Box size must be set in accordance to the selected star size. Box: false Disconnect from external guiding application. Disconnect Swap DEC direction pulses. This value is determined automatically from the calibration procedure, only override if necessary. Swap 8 16 32 64 128 Guide camera binning. It is recommended to set binning to 2x2 or higher. Bin: Qt::AlignCenter Directions Apply filter to image after capture to enhance it Effects 2 North Direction Guiding + South Direction Guiding - Guide Declination Axis DEC Guide Right Ascention Axis RA Guider: 3 60.000000000000000 1.000000000000000 -- false Connect to external guiding application. Connect 2 East Direction Guiding + West Direction Guiding - Guide Info 3 3 3 3 3 3 Scope: 0 0 <html><head/><body><p>Select which telescope to use when performing Field of View calculations.</p></body></html> Primary Scope Guide Scope 1 Guiding rate true 24 24 Mount guiding rate. Find out the guiding rate used by your mount and update the value here to get the <b>recommended</b> value of proportional gain suitable for your mount. Setting this value <b>does not</b> change your mount guiding rate. .. 24 24 true Mount guiding rate (x15"/sec) 3 0.100000000000000 2.000000000000000 0.100000000000000 <b>Recommended</b> proportional rate given the selected guiding rate. P=xxx Qt::AlignCenter Qt::Horizontal 3 Focal Focal Length (mm) QFrame::StyledPanel QFrame::Sunken xxx false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Aperture Aperture (mm) QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Focal Ratio F/D QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter FOV Field of View (arcmin) QFrame::StyledPanel QFrame::Sunken YYxYY Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Qt::Horizontal 3 Pulse Length (ms) Guiding Delta " 1 Generated RA pulse QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Generated DEC pulse QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 1 Immediate Guiding RA deviation in arcseconds QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Immediate Guiding DEC deviation in arcseconds QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter RA Guiding RMS error RA RMS" QFrame::StyledPanel QFrame::Sunken xxx false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter DEC Guiding RMS error DE RMS" QFrame::StyledPanel QFrame::Sunken xxx false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter <b>Total RMS"</b> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter font-weight:bold; QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignCenter Qt::Vertical 20 40 1 0 0 0 Qt::Vertical 0 0 320 240 40 30 Qt::Horizontal 2 2 Drift Graphics 1 3 3 3 3 0 Qt::Horizontal QSizePolicy::Minimum 1 20 200 200 16777215 16777215 Qt::Horizontal QSizePolicy::Minimum 1 20 <html><head/><body><p>Drag the slider to adjust the scale of the Corrections Graphs relative to the scale of the drift graphs.</p></body></html> 1 500 200 50 Qt::Vertical true false 0 0 225 200 1 Control parameters 0 0 0 0 Control Parameters 3 3 3 3 3 Qt::Horizontal 1 <html><head/><body><p>Proportional term accounts for present values of the error. For example, if the error is large and positive, the guider corrective pulse will also be large and positive.</p></body></html> Proportional gain 1 1000.000000000000000 1 1000.000000000000000 <html><head/><body><p>Integral term accounts for past values of the error. For example, if the current pulse is not sufficiently strong, the integral of the error will accumulate over time, and the guider will respond by applying a stronger action.</p></body></html> Integral gain 1 1000.000000000000000 1 1000.000000000000000 false Derivative term accounts for possible future trends of the error, based on its current rate of change. Derivative gain false 1 1000.000000000000000 false 1 1000.000000000000000 <html><head/><body><p>Maximum guide pulse that is generated by the guider and sent to the mount.</p></body></html> Maximum pulse 9999 9999 <html><head/><body><p>Minimum guide pulse that is sent to the mount. If the generated pulse is less than this value, then no pulse is sent to the mount.</p></body></html> Minimum pulse 9999 9999 false AO Limits false Maximum deviation to correct for using Adaptive Optics unit. If the guiding deviation exceeds this value, Ekos will guide the mount mechanically 1 30.000000000000000 0.500000000000000 2.000000000000000 false arcsecs Drift Plot 0 0 0 0 0 0 200 200 3 0 16777215 20 Graph: 2 0 <html><head/><body><p>Display the RA graph in the Drift Graphics plot.</p></body></html> RA true <html><head/><body><p>Display the RA Corrections graph in the Drift Graphics plot.</p></body></html> Corr 2 0 <html><head/><body><p>Display DEC graph in the Drift Graphics plot.</p></body></html> DEC true <html><head/><body><p>Display the DEC Corrections graph in the Drift Graphics plot.</p></body></html> Corr Trace: <html><head/><body><p>Drag the slider to scroll through guide history while displaying the RA and DEC error points on both graphs. Dragging to the far right will set the guide plots to display the latest guide data and autoscroll the graph.</p></body></html> 10 10 Qt::Horizontal <html><head/><body><p>Check to display the latest guide data and autoscroll the graph.</p></body></html> Max true 32 31 32 32 <html><head/><body><p>Autoscale both Guide Graphs to their default scale. If any points are located outside this range, the view is expanded to include them (with the exception of the time axis in the drift graphics).</p></body></html> 32 32 32 32 <html><head/><body><p>Export the guide data from the current session to a CSV file readable by a spreadsheet program.</p></body></html> 32 32 32 32 <html><head/><body><p>Clear all the recent guide data.</p></body></html> <html><head/><body><p>Set the desired guiding accuracy in the Drift Plot. The number represents the radius of the green concentric circle in arcseconds.</p></body></html> 1 20.000000000000000 0.500000000000000 2.000000000000000 QCustomPlot QWidget
auxiliary/qcustomplot.h
1
NonLinearDoubleSpinBox QDoubleSpinBox
auxiliary/nonlineardoublespinbox.h
captureB loopB showFITSViewerB guideB clearCalibrationB stopB guiderCombo ST4Combo exposureIN binningCombo boxSizeCombo filterCombo checkBox_DirRA checkBox_DirDEC swapCheck eastControlCheck westControlCheck northControlCheck southControlCheck externalConnectB externalDisconnectB FOVScopeCombo spinBox_GuideRate showRAPlotCheck showDECPlotCheck showRACorrectionsCheck showDECorrectionsCheck guideSlider correctionSlider tabWidget latestCheck guideAutoScaleGraphB guideSaveDataB guideDataClearB accuracyRadiusSpin spinBox_AOLimit spinBox_MaxPulseDEC spinBox_PropGainDEC spinBox_MinPulseDEC spinBox_MaxPulseRA spinBox_IntGainDEC spinBox_PropGainRA spinBox_IntGainRA spinBox_DerGainRA spinBox_DerGainDEC spinBox_MinPulseRA
diff --git a/kstars/ekos/guide/guideinterface.h b/kstars/ekos/guide/guideinterface.h index 92a652a49..b28e630ac 100644 --- a/kstars/ekos/guide/guideinterface.h +++ b/kstars/ekos/guide/guideinterface.h @@ -1,85 +1,87 @@ /* Ekos guide class interface Copyright (C) 2016 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "ekos/ekos.h" #include #include #include class QString; namespace Ekos { /** * @class GuideInterface * @short Interface skeleton for implementation of different guiding applications and/or routines * * @author Jasem Mutlaq * @version 1.0 */ class GuideInterface : public QObject { Q_OBJECT public: GuideInterface() = default; virtual ~GuideInterface() override = default; virtual bool Connect() = 0; virtual bool Disconnect() = 0; virtual bool isConnected() = 0; virtual bool calibrate() = 0; virtual bool guide() = 0; virtual bool suspend() = 0; virtual bool resume() = 0; virtual bool abort() = 0; virtual bool dither(double pixels) = 0; virtual bool clearCalibration() = 0; virtual bool reacquire() { return false; } virtual bool setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength); virtual bool getGuiderParams(double *ccdPixelSizeX, double *ccdPixelSizeY, double *mountAperture, double *mountFocalLength); virtual bool setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY); virtual bool getFrameParams(uint16_t *x, uint16_t *y, uint16_t *w, uint16_t *h, uint16_t *binX, uint16_t *binY); virtual void setStarPosition(QVector3D& starCenter); signals: void newLog(const QString &); void newStatus(Ekos::GuideState); void newAxisDelta(double delta_ra, double delta_dec); void newAxisSigma(double sigma_ra, double sigma_dec); void newAxisPulse(double pulse_ra, double pulse_dec); void newStarPosition(const QVector3D &newCenter, bool updateNow); void newStarPixmap(QPixmap &); void frameCaptureRequested(); + void guideEquipmentUpdated(); + protected: Ekos::GuideState state { GUIDE_IDLE }; double ccdPixelSizeX { 0 }; double ccdPixelSizeY { 0 }; double mountAperture { 0 }; double mountFocalLength { 0 }; uint16_t subX { 0 }; uint16_t subY { 0 }; uint16_t subW { 0 }; uint16_t subH { 0 }; uint16_t subBinX { 1 }; uint16_t subBinY { 1 }; }; } diff --git a/kstars/ekos/guide/opsguide.cpp b/kstars/ekos/guide/opsguide.cpp index c25f3b013..66e0f1c6d 100644 --- a/kstars/ekos/guide/opsguide.cpp +++ b/kstars/ekos/guide/opsguide.cpp @@ -1,51 +1,45 @@ /* INDI Options Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com) 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 "opsguide.h" #include "Options.h" #include "kstars.h" #include "auxiliary/ksnotification.h" #include "internalguide/internalguider.h" #include #include #include #include #include #include namespace Ekos { OpsGuide::OpsGuide() : QFrame(KStars::Instance()) { setupUi(this); //Get a pointer to the KConfigDialog m_ConfigDialog = KConfigDialog::exists("guidesettings"); - connect(kcfg_GuideRemoteImagesEnabled, &QCheckBox::toggled, this, [this] () - { - if (Options::guideRemoteImagesEnabled() != kcfg_GuideRemoteImagesEnabled->isChecked()) - KSNotification::info(i18n("You must restart KStars for this change to take effect.")); - }); - connect(kcfg_DitherNoGuiding, &QCheckBox::toggled, this, [&](bool checked) { if (checked && kcfg_DitherEnabled->isChecked()) { KSNotification::error("Guided dithering cannot be used along with non-guided dithering."); kcfg_DitherEnabled->setChecked(false); } }); connect(m_ConfigDialog, SIGNAL(settingsChanged(QString)), this, SIGNAL(settingsUpdated())); } } diff --git a/kstars/ekos/guide/opsguide.ui b/kstars/ekos/guide/opsguide.ui index 6305a357b..83cc4c4f0 100644 --- a/kstars/ekos/guide/opsguide.ui +++ b/kstars/ekos/guide/opsguide.ui @@ -1,497 +1,484 @@ OpsGuide 0 0 - 274 - 455 + 299 + 500 3 3 3 3 3 Settings 3 3 3 3 3 Algorithm + + + + 0.500000000000000 + + + 30.000000000000000 + + + 10.000000000000000 + + + + + + + Maximum delta RMS permitted before stopping guide process and searching for new guide stars. + + + Max Delta RMS + + + + + + + <html><head/><body><p>If star tracking is lost due to passing clouds or other reasons, wait this many seconds before giving up.</p></body></html> + + + Lost Star timeout + + + + + + + false + + + Rapid Guide + + + 0 0 Smart SEP Fast Auto Threshold No Threshold - - - - <html><head/><body><p>If star tracking is lost due to passing clouds or other reasons, wait this many seconds before giving up.</p></body></html> - + + - Lost Star timeout + seconds 3600 10 60 - - - - seconds - - - - - - - Maximum delta RMS permitted before stopping guide process and searching for new guide stars. - - - Max Delta RMS - - - - - - - 0.500000000000000 - - - 30.000000000000000 - - - 10.000000000000000 - - - arcsecs - - - - true - - - For external guiders, enable receiving guide images in Ekos. - - - Receive external guide frames - - - - - - - false - - - Rapid Guide - - - Dither 3 Number of pixels to move the guiding square in a random direction. 0.100000000000000 10.000000000000000 0.100000000000000 2.000000000000000 After dither is successful, wait for this many seconds before proceeding. Settle seconds Threshold time limit for successful dither settling 360 Move locked guiding square location after frame capture Dither pixels Maximum allowable distance for guiding to be considered settled. 50.000000000000000 seconds pixels Timeout After dither is successful, wait for this many seconds before proceeding. 360 Dither after this many captured frames in the CCD module 1 60 frames Dither after this many captured frames in the CCD module Frequency <html><head/><body><p>Maximum dithering iteration attempts before giving up</p></body></html> Max. Iterations <html><head/><body><p>Maximum dithering iteration attempts before giving up</p></body></html> 1 30 <html><head/><body><p>If checked, autoguiding is aborted when dithering fails. Otherwise, guiding resumes normally.</p></body></html> Abort Autoguide on failure <html><head/><body><p>Perform dithering when not guiding.</p></body></html> Non-Guide Dither Pulse Pulse length in milliseconds to command mount motion in a random direction 10 10000 100 ms false Image Guiding Use Image Guiding Region Axis: 64 128 256 512 1024 Qt::Horizontal 40 20 Qt::Vertical 20 40 diff --git a/kstars/kstars.kcfg b/kstars/kstars.kcfg index fdabddbf6..fe7fa81ab 100644 --- a/kstars/kstars.kcfg +++ b/kstars/kstars.kcfg @@ -1,2443 +1,2439 @@ ksutils.h The screen coordinates of the Time InfoBox. QPoint(0,0) The screen coordinates of the Focus InfoBox. QPoint(600,0) The screen coordinates of the Geographic Location InfoBox. QPoint(0,600) If true, the Time InfoBox will show only its top line of data. true If true, the Focus InfoBox will show only its top line of data. true If true, the Geographic Location InfoBox will show only its top line of data. true Toggles display of all three InfoBoxes. true Toggles display of the Time InfoBox. true Toggles display of the Focus InfoBox. true Toggles display of the Geographic Location InfoBox. true Is the Time InfoBox anchored to a window edge? 0 = not anchored; 1 = anchored to right edge; 2 = anchored to bottom edge; 3 = anchored to bottom and right edges. 0 0 3 Is the Focus InfoBox anchored to a window edge? 0 = not anchored; 1 = anchored to right edge; 2 = anchored to bottom edge; 3 = anchored to bottom and right edges. 1 0 3 Is the Geographic Location InfoBox anchored to a window edge? 0 = not anchored; 1 = anchored to right edge; 2 = anchored to bottom edge; 3 = anchored to bottom and right edges. 2 0 3 Toggle display of the status bar. true Toggle display of the Horizontal coordinates of the mouse cursor in the status bar. true Toggle display of the Equatorial coordinates of the mouse cursor at the current epoch in the status bar. true Toggle display of the Equatorial coordinates of the mouse cursor at the standard epoch in the status bar. false true 1024 768 true Black Body List of the filenames of custom object catalogs. List of integers toggling display of each custom object catalog (any nonzero value indicates the objects in that catalog will be displayed). List of names for which custom catalogs are to be displayed. Names of objects entered into the find dialog are resolved using online services and stored in the database. This option also toggles the display of such resolved objects on the sky map. true 800 600 true true false Toggle display of crosshairs centered at telescope's pointed position in the KStars sky map. true Toggle display of INDI messages in the KStars statusbar. true false Show INDI messages as desktop notifications instead of dialogs. false true false false The default location of saved FITS files KSUtils::getDefaultPath("fitsDir") INDI server will attempt to bind with ports starting from this port 7624 INDI server will attempt to bind with ports ending with this port 9000 List of the aliases for filter wheel slots. PATH to indiserver binary KSUtils::getDefaultPath("indiServer") false PATH to indi drivers directory KSUtils::getDefaultPath("indiDriversDir") false 320 240 false false false false false false false false false false false The City name of the current geographic location. Greenwich The Province name of the current geographic location. This is the name of the state for locations in the U. S. The Country name of the current geographic location. United Kingdom The longitude of the current geographic location, in decimal degrees. 0.0 The latitude of the current geographic location, in decimal degrees. 51.468 -10.0 0.0 Two-letter code that determines the dates on which daylight savings time begins and ends (you can view the rules by pressing the "Explain DST Rules" button in the Geographic Location window). -- If true, focus changes will cause the sky to visibly spin to the new position. Otherwise, the display will "snap" instantly to the new position. true If true, clicking on the skymap will select the closest object and highlights it. false Type of cursor when exploring the sky map. 1 The names of the currently selected field-of-view indicators. The list of defined FOV indicator names is listed in the "Settings|FOV Symbols" menu. Telrad If true, trails attached to solar system bodies will fade into the background sky color. true The right ascension of the initial focus position of the sky map, in decimal hours. This value is volatile; it is reset whenever the program shuts down. 180.0 The declination of the initial focus position of the sky map, in decimal degrees. This value is volatile; it is reset whenever the program shuts down. 45.0 The name of the object that should be centered and tracked on startup. If no object should be centered, set to "nothing". This value is volatile; it is reset whenever the program shuts down. nothing True if the skymap should track on its initial position on startup. This value is volatile; it is reset whenever the program shuts down. false Toggle whether KStars should hide some objects while the display is moving, for smoother motion. true Toggle whether constellation boundaries are hidden while the display is in motion. true Toggle whether constellation lines are hidden while the display is in motion. false Choose sky culture. 11 Toggle whether constellation names are hidden while the display is in motion. false Toggle whether the coordinate grids are hidden while the display is in motion. true Toggle whether the Milky Way contour is hidden while the display is in motion. true Toggle whether IC objects are hidden while the display is in motion. true Toggle whether Messier objects are hidden while the display is in motion. true Toggle whether NGC objects are hidden while the display is in motion. true Toggle whether extra objects are hidden while the display is in motion. true Toggle whether solar system objects are hidden while the display is in motion. false Toggle whether faint stars are hidden while the display is in motion. true Toggle whether name labels are hidden while the display is in motion. true Toggle whether asteroids are drawn in the sky map. true Toggle whether asteroid name labels are drawn in the sky map. false true Toggle whether comets are drawn in the sky map. true Toggle whether comet comas are drawn in the sky map. true Toggle whether comet name labels are drawn in the sky map. false Toggle whether supernovae are drawn in the sky map. false Toggle whether supernova name labels are drawn in the sky map. false Set magnitude limit for supernovae to be shown on the skymap. 16 Toggle supernova alerts. true Set magnitude limit for supernovae to be alerted. 13 Toggle whether constellation boundaries are drawn in the sky map. false Toggle whether constellation boundary containing the central focus point is highlighted in the sky map. false Toggle whether constellation lines are drawn in the sky map. false Toggle whether constellation art drawn in the sky map. false Toggle whether constellation name labels are drawn in the sky map. false Toggle whether deep-sky objects are drawn in the sky map. true Toggle whether the ecliptic line is drawn in the sky map. false Toggle whether the equator line is drawn in the sky map. false Coordinate grids will automatically change according to active coordinate system. true Toggle whether the equatorial coordinate grid is drawn in the sky map. false Toggle whether the horizontal coordinate grid is drawn in the sky map. false Toggle whether the local meridian line is drawn in the sky map. false Toggle whether the region below the horizon is opaque. true Toggle whether the horizon line is drawn in the sky map. true Toggle whether flags are drawn in the sky map. true Toggle whether IC objects are drawn in the sky map. false Toggle whether NGC objects are drawn in the sky map. true Toggle whether Messier objects are drawn in the sky map. true Toggle whether Messier objects are rendered as images in the sky map. true Toggle whether extra objects are drawn in the sky map. true Toggle whether the Milky Way contour is drawn in the sky map. true Toggle whether the Milky Way contour is filled. When this option is false, the Milky Way is shown as an outline. true Meta-option to control whether all major planets (and the Sun and Moon) are drawn in the sky map. true Toggle whether major planets (and the Sun and Moon) are rendered as images in the sky map. true Toggle whether major planets (and the Sun and Moon) are labeled in the sky map. true Toggle whether the Sun is drawn in the sky map. true Toggle whether the Moon is drawn in the sky map. true Toggle whether Mercury is drawn in the sky map. true Toggle whether Venus is drawn in the sky map. true Toggle whether Mars is drawn in the sky map. true Toggle whether Jupiter is drawn in the sky map. true Toggle whether Saturn is drawn in the sky map. true Toggle whether Uranus is drawn in the sky map. true Toggle whether Neptune is drawn in the sky map. true Toggle whether Pluto is drawn in the sky map. true Toggle whether stars are drawn in the sky map. true Toggle whether star magnitude (brightness) labels are shown in the sky map. false Toggle whether star name labels are shown in the sky map. true Toggle whether deep-sky object magnitude (brightness) labels are shown in the sky map. false Toggle whether deep-sky object name labels are shown in the sky map. false The timescale above which slewing mode is forced on at all times. 60 The background fill mode for the on-screen information boxes: 0="no BG"; 1="semi-transparent BG"; 2="opaque BG" 1 Algorithm for the mapping projection. 0 Use official IAU abbreviations for constellation names. false Use Latin constellation names. false Use localized constellation names (if localized names are not available, default to Latin names). true Display the sky with horizontal coordinates (when false, equatorial coordinates will be used). true Toggle whether a centered object automatically gets a name label attached. true Toggle whether a centered solar system object automatically gets a trail attached, as long as it remains centered. true Toggle whether the object under the mouse cursor gets a transient name label. true Toggle whether object positions are corrected for the effects of atmospheric refraction (only applies when horizontal coordinates are used). true Toggle whether corrections due to bending of light around the sun are taken into account false Toggle whether the sky is rendered using antialiasing. Lines and shapes are smoother with antialiasing, but rendering the screen will take more time. true The zoom level, measured in pixels per radian. 250. 250. 5000000. When zooming in or out, change zoom speed factor by this multiplier. 0.2 0.01 1.0 The faint magnitude limit for drawing asteroids. 15.0 The maximum magnitude (visibility) to filter the asteroid data download from JPL. 12.000 Controls the relative number of asteroid name labels drawn in the map. 4.0 The faint magnitude limit for drawing deep-sky objects, when fully zoomed in. 16.0 The faint magnitude limit for drawing deep-sky objects, when fully zoomed out. 5.0 When enabled, objects whose magnitudes are unknown, or not available to KStars, are drawn irrespective of the faint limits set. true Sets the density of stars in the field of view 5 The faint magnitude limit for drawing stars, when the map is in motion (only applicable if faint stars are set to be hidden while the map is in motion). 5.0 The relative density for drawing star name and magnitude labels. 2.0 The relative density for drawing deep-sky object name and magnitude labels. 5.0 If true, long names (common names) for deep-sky objects are shown in the labels. false The maximum solar distance for drawing comets. 3.0 Use experimental OpenGL backend (deprecated). false The state of the clock (running or not) true Objects in the observing list will be highlighted with a symbol in the map. true Objects in the observing list will be highlighted with a colored name label in the map. false The observing list will prefer DSS imagery while downloading imagery. true The observing list will prefer SDSS imagery while downloading imagery. false Check this if you use a large Dobsonian telescope. Sorting by percentage current altitude is an easy way of determining what objects are well-placed for observation. However, when using a large Dobsonian telescope, objects close to the zenith are hard to observe. Since tracking there corresponds to a rotation in azimuth, it is both counterintuitive and requires the observer to frequently move the ladder. The region around the zenith where this is particularly frustrating is called the Dobsonian hole. This checkbox makes the observing list consider objects present in the hole as unfit for observation. false This specifies the angular radius of the Dobsonian hole, i.e. the region where a large Dobsonian telescope cannot be pointed easily. 15.00 40.00 The name of the color scheme moonless-night.colors The method for rendering stars: 0="realistic colors"; 1="solid red"; 2="solid black"; 3="solid white"; 4="solid real colors" 0 4 The color saturation level of stars (only applicable when using "realistic colors" mode). 6 10 The color for the angular-distance measurement ruler. #FFF The background color of the on-screen information boxes. #000 The text color for the on-screen information boxes, when activated by a mouse click. #F00 The normal text color of the on-screen information boxes. #FFF The color for the constellation boundary lines. #222 The color for the constellation boundary lines. #222 The color for the constellation figure lines. #555 The color for the constellation names. #AA7 The color for the cardinal compass point labels. #002 The color for the ecliptic line. #663 The color for the equator line. #FFF The color for the equatorial coordinate grid lines. #456 The color for the horizontal coordinate grid lines. #5A3 The color for objects which have extra URL links available. #A00 The color for the horizon line and opaque ground. #5A3 The color for the local meridian line. #0059b3 The color for Messier object symbols. #0F0 The color for NGC object symbols. #066 The color for IC object symbols. #439 The color for the Milky Way contour. #123 The color for star name labels. #7AA The color for deep-sky object name labels. #7AA The color for solar system object labels. #439 The color for solar system object trails. #963 The color for the sky background. #002 The color for the artificial horizon region. #C82828 The color for telescope target symbols. #8B8 Color of visible satellites. #00FF00 Color of invisible satellites. #FF0000 Color of satellites labels. #640000 Color of supernova #FFA500 The color for user-added object labels. #439 The color for RA Guide Error bar in Ekos guide module. #00FF00 The color for DEC Guide Error bar in Ekos guide module. #00A5FF The color for solver FOV box in Ekos alignment module. #FFFF00 false Xplanet binary path KSUtils::getDefaultPath("XplanetPath") Option to use a FIFO file instead of saving to the hard disk true How long to wait for XPlanet before giving up in milliseconds 1000 How long to pause between frames in the XPlanet Animation 100 Width of xplanet window 640 Height of xplanet window 480 If true, display a label in the upper right corner. false Show local time. true Show GMT instead of local time. false Specify the text of the first line of the label. By default, it says something like "Looking at Earth". Any instances of %t will be replaced by the target name, and any instances of %o will be replaced by the origin name. Specify the point size. 12 Set the color for the label. #F00 Specify the format for the date/time label. This format string is passed to strftime(3). The default is "%c %Z", which shows the date, time, and time zone in the locale’s appropriate date and time representation. %c %Z false true false false Draw a glare around the sun with a radius of the specified value larger than the Sun. The default value is 28. 28 Place the observer above a random latitude and longitude false Place the observer above the specified longitude and latitude true Render the target body as seen from above the specified latitude (in degrees). The default value is 0. 0 Place the observer above the specified longitude (in degrees). Longitude is positive going east, negative going west (for the earth and moon), so for example Los Angeles is at -118 or 242. The default value is 0. 0 The default is no projection. Multiple bodies will not be shown if this option is specified, although shadows will still be drawn. 0 Use a file as the background image, with the planet to be superimposed upon it. This option is only meaningful with the -projection option. A color may also be supplied. false Use a file as the background image. false The path of the background image. Use a color as the background. true The color of the background. #000 A star of the specified magnitude will have a pixel brightness of 1. The default value is 10. Stars will be drawn more brightly if this number is larger. 10 If checked, use an arc file to be plotted against the background stars. false Specify an arc file to be plotted against the background stars. If checked, use a config file. false Use the specified configuration file. If checked, use kstars's FOV. false If checked, use the specified marker file. false Specify a file containing user-defined marker data to display against the background stars. If checked, write coordinates of the bounding box for each marker in a file. false Write coordinates of the bounding box for each marker to this file. If checked, use star map file to draw the background stars. false Star map file path This option is only used when creating JPEG images. The quality can range from 0 to 100. The default value is 80. 80 Toggle whether satellite tracks are drawn in the sky map. false Toggle whether satellite tracks are drawn in the sky map. false If selected, satellites will be draw like stars, otherwise, draw satellites as small colored square. false Toggle whether satellite labels are drawn in the sky map. false List of selected satellites. Checking this option causes recomputation of current equatorial coordinates from catalog coordinates (i.e. application of precession, nutation and aberration corrections) for every redraw of the map. This makes processing slower when there are many stars to handle, but is more likely to be bug free. There are known bugs in the rendering of stars when this recomputation is avoided. false The default size for DSS images downloaded from the Internet. 15.0 To include parts of the star field, we add some extra padding around DSS images of deep-sky objects. This option configures the total (both sides) padding added to either dimension of the field. 10.0 Checking this option causes KStars to generate verbose debug information for diagnostic purposes. This may cause slowdown of KStars. false Checking this option causes KStars to generate regular debug information. true Checking this option causes KStars to stop generating ANY debug information. false Checking this option causes KStars log debug messages to the default output used by the platform (e.g. Standard Error). true Checking this option causes KStars log debug messages to a log file as specified. false Log FITS Data activity. false Log INDI devices activity. false Log Ekos Capture Module activity. false Log Ekos Focus Module activity. false Log Ekos Guide Module activity. false Log Ekos Alignment Module activity. false Log Ekos Mount Module activity. false Log Ekos Observatory Module activity. false true Display all captured FITS images in a single tab instead of multiple tabs per image. true Display all captured FITS images in a single FITS Viewer window. By default each camera create its own FITS Viewer instance false Display all opened FITS images in a single FITS Viewer window. true Bring the FITSViewer window to the foreground when receiving a new image. true false !KSUtils::isHardwareLimited() false !KSUtils::isHardwareLimited() !KSUtils::isHardwareLimited() KSUtils::isHardwareLimited() 4 false false 40.0 0 600 600 true false true true true false Simulators false true false true 1 Minimum telescope altitude limit. If the telescope is below this limit, it will be commanded to stop. 0 Maximum telescope altitude limit. If the telescope is above this limit, it will be commanded to stop. 90.0 false If the target hour angle exceeds this value, Ekos will command a meridian flip and if successful it will resume guiding and capture operations. false true false false false 3:00 AM false 1 0 If guide deviation exceeds this limit, the exposure will be automatically aborted and only resumed when the deviation is within this limit. 2 If HFR deviation exceeds this limit, the autofocus routine will be automatically started. 0.5 false false false Sets the time interval before forced autofocus attempts during a capture sequence. 60 false If set, Ekos will capture a few flat images to determine the optimal exposure time to achieve the desired ADU value. 0 Maximum difference between measured and target ADU values to deem the value as acceptable. 1000 0 0 0 0 0.1 0 false false 2.5 true false 1 30 true !KSUtils::isHardwareLimited() !KSUtils::isHardwareLimited() 0 KSUtils::getDefaultPath("fitsDir") 60 false false false Step size of the absolute focuser. The step size TICKS should be adjusted so that when the focuser moves TICKS steps, the difference in HFR is more than 0.1 pixels. Lower the value when you are close to optimal focus. 100 Wait for this many seconds after moving the focuser before capturing the next image during AutoFocus. 0 Wait for this many seconds after resuming guide. 0 The tolerance specifies the percentage difference between the current focusing position and the minimum obtained during the focusing run. Adjustment of this value is necessary to prevent the focusing algorithm from oscillating back and forth. 1 Set the maximum travel distance of an absolute focuser. 10000 Specifies gain value of CCD when performing focusing if supported by camera. 0 Set box size to select a focus star. 64 Set horizontal binning of CCD camera while in focus mode. 1 Set vertical binning of CCD camera while in focus mode. 1 true false During full field focusing, stars which are inside this percentage of the frame are filtered out of HFR calculation (default 0%). Detection algorithms may also have an inherent filter. 0.0 During full field focusing, stars which are outside this percentage of the frame are filtered out of HFR calculation (default 100%). Detection algorithms may also have an inherent filter. 100.0 false true false 0 150 0 0 1 Specifies exposure value of CCD in seconds when performing plate solving. 1 Set binning index of CCD camera while in alignment mode. Default values 0-3 corresponding to 1x1 to 4x4 binning. 4 is max binning. 4 Use rotator when performing load and slew. false Threshold between measured and FITS position angles in arcminutes to consider the load and slew operation successful. 30 0 0 1 true false false 30 0 false 1500 false true true true true 1 true 2 true true true 30 false Path to astrometry.net solver location. KSUtils::getDefaultPath("AstrometrySolverBinary") false Path to astrometry.net wcsinfo location. KSUtils::getDefaultPath("AstrometryWCSInfo") false Path to astrometry.net file location. KSUtils::getDefaultPath("AstrometryConfFile") true true Folder in which the desired python executable or link to be used for astrometry.net resides. /usr/local/opt/python/libexec/bin Key to access astrometry.net online web services. You must register with astrometry.net to obtain a key. iczikaqstszeptgs http://nova.astrometry.net true 180 -1 true 1.0 0 0 localhost 4400 localhost 5656 0 1000 2 false 1 false false - - - false - 3 60 10 true false false false 2 1 0 1 45 10 500 false false false 2 true true true true true true 133.33 133.33 0 0 0 0 5000 5000 100 100 0.5 2 true true false false Log Ekos Scheduler Module activity. false Sort scheduler jobs by priority and altitude. true true false false false false true false 2 true 5 30 3 0 0 0 0 0 0 0 0 1 0 false 7624 8624 300 1000 None false false Toggle whether the HIPS sources are drawn in the sky map. false true true false false true 600 true true true 30 true true true