diff --git a/kstars/ekos/guide/externalguide/phd2.cpp b/kstars/ekos/guide/externalguide/phd2.cpp index 54390631d..e09085cf5 100644 --- a/kstars/ekos/guide/externalguide/phd2.cpp +++ b/kstars/ekos/guide/externalguide/phd2.cpp @@ -1,779 +1,939 @@ /* 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 "fitsio.h" #include "ekos/ekosmanager.h" #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["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["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 + //get_connected + //get_cooler_status + //get_current_equipment + //get_dec_guide_mode + methodResults["get_exposure"] = EXPOSURE_TIME; + //get_exposure_durations + //get_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 + //save_image + //set_algo_param + methodResults["set_connected"] = CONNECTION_RESULT; + //set_dec_guide_mode + //set_exposure + //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)); } PHD2::~PHD2() { } bool PHD2::Connect() { if (connection == DISCONNECTED) { connection = CONNECTING; tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port()); } // Already connected, let's connect equipment else setEquipmentConnected(true); return true; } bool PHD2::Disconnect() { if (connection == EQUIPMENT_CONNECTED) setEquipmentConnected(false); connection = DISCONNECTED; 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())); } connection = DISCONNECTED; } void PHD2::readPHD2() { QTextStream stream(tcpSocket); QJsonParseError qjsonError; while (stream.atEnd() == false) { QString rawString = stream.readLine(); if (rawString.isEmpty()) continue; QJsonDocument jdoc = QJsonDocument::fromJson(rawString.toLatin1(), &qjsonError); if (qjsonError.error != QJsonParseError::NoError) { + //This will remove a method that had errors from the request list. + if(rawString.contains("\"id\":")) + { + int idLocation = rawString.indexOf("\"id\":") + 5; + int endOfID = rawString.indexOf("}" , idLocation) - idLocation; + QString idString = rawString.mid(idLocation, endOfID); + int id = idString.toInt(); + for(int i = 0; i < resultRequests.size(); i++){ + QPair request = resultRequests.at(i); + if(request.first == id){ + resultRequests.remove(i); + break; + } + } + } + //So we don't spam the error log with image frames that accidentally get broken up. if(rawString.contains("frame")) //This prevents it from printing the first line of the error. blockLine2=true; //This will set it to watch for the second line to cause an error. else if(blockLine2) //This will prevent it from printing the second line of the error. blockLine2=false; //After avoiding printing the error message, this will set it to look for errors again. else { //This will still print other parsing errors that don't involve image frames. emit newLog(rawString); emit newLog(qjsonError.errorString()); } continue; } processJSON(jdoc.object(), rawString); } } void PHD2::processJSON(const QJsonObject &jsonObj, QString rawString) { PHD2MessageType messageType = PHD2_UNKNOWN; if (jsonObj.contains("Event")) { messageType = PHD2_EVENT; processPHD2Event(jsonObj); if (event == Alert) return; } else if (jsonObj.contains("error")) { messageType = PHD2_ERROR; processPHD2Error(jsonObj); } else if (jsonObj.contains("result")) { - //This is a special result/message type, the Star Image. We want to handle it separately from the others so it doesn't print massive amounts of data to the log. - QJsonObject jsonResult = jsonObj["result"].toObject(); - if (jsonResult.contains("frame")) - { - messageType = PHD2_STAR_IMAGE; - processStarImage(jsonResult); - return; + messageType = PHD2_RESULT; + PHD2ResultType resultRequest = NO_RESULT; + int id = jsonObj["id"].toInt(); + + for(int i = 0; i < resultRequests.size(); i++){ + QPair request = resultRequests.at(i); + if(request.first == id){ + resultRequest = methodResults.value(request.second); + resultRequests.remove(i); + break; + } } - else - messageType = PHD2_RESULT; - } - qCDebug(KSTARS_EKOS_GUIDE) << rawString; + if(resultRequest != STAR_IMAGE) //This is so we don't spam the log with Image Data. + qCDebug(KSTARS_EKOS_GUIDE) << rawString; - if(messageType == PHD2_RESULT){ - switch (desiredResult) - { + switch (resultRequest) + { case NO_RESULT: - case CONNECTION_RESULT: - desiredResult=NO_RESULT; - //These will be handled below in the next switch statement. - break; - - case PIXEL_SCALE: - pixelScale=jsonObj["result"].toDouble(); - desiredResult=NO_RESULT; + //Ekos didn't ask for this result? return; case EXPOSURE_TIME: + { int exposurems=jsonObj["result"].toInt(); KStars::Instance()->ekosManager()->guideModule()->setExposure(exposurems/1000.0); - desiredResult=NO_RESULT; return; + } + + case PIXEL_SCALE: + pixelScale=jsonObj["result"].toDouble(); + return; + + case STAR_IMAGE: + { + QJsonObject jsonResult = jsonObj["result"].toObject(); + if (jsonResult.contains("frame")) + { + processStarImage(jsonResult); + } + return; + } + + case CONNECTION_RESULT: + //These will be handled below in the next switch statement. + break; + + //For now these are not handled, they should just return 0 if the command was received. + //These could be changed if Ekos should do something when the command is received. + case CLEAR_CALIBRATION_COMMAND_RECEIVED: + case DITHER_COMMAND_RECEIVED: + case GUIDE_COMMAND_RECEIVED: + case SET_PAUSED_COMMAND_RECEIVED: + case STOP_CAPTURE_COMMAND_RECEIVED: + return; } } switch (connection) { case CONNECTING: if (event == Version){ connection = CONNECTED; if(pixelScale==0) requestPixelScale(); } return; case CONNECTED: // If initial state is STOPPED, let us connect equipment if(pixelScale==0) requestPixelScale(); if (state == STOPPED || state == PAUSED) { setEquipmentConnected(true); } else if (state == GUIDING || state == DITHERING) { connection = EQUIPMENT_CONNECTED; emit newStatus(Ekos::GUIDE_CONNECTED); } return; case DISCONNECTED: emit newStatus(Ekos::GUIDE_DISCONNECTED); break; case EQUIPMENT_CONNECTING: if (messageType == PHD2_RESULT) { connection = EQUIPMENT_CONNECTED; emit newStatus(Ekos::GUIDE_CONNECTED); requestPixelScale(); } else if (messageType == PHD2_ERROR) { connection = EQUIPMENT_DISCONNECTED; emit newStatus(Ekos::GUIDE_DISCONNECTED); } return; case EQUIPMENT_CONNECTED: case EQUIPMENT_DISCONNECTED: break; case EQUIPMENT_DISCONNECTING: connection = EQUIPMENT_DISCONNECTED; //emit disconnected(); return; } switch (state) { case GUIDING: break; case PAUSED: break; case STOPPED: break; default: break; } } void PHD2::processPHD2Event(const QJsonObject &jsonEvent) { QString eventName = jsonEvent["Event"].toString(); if (events.contains(eventName) == false) { 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())); break; case CalibrationComplete: //state = CALIBRATION_SUCCESSFUL; // It goes immediately to guiding until PHD implements a calibration-only method state = GUIDING; emit newLog(i18n("PHD2: Calibration Complete.")); //emit guideReady(); emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); break; case StartGuiding: state = GUIDING; if (connection != EQUIPMENT_CONNECTED) { setConnectedRetries = 0; connection = EQUIPMENT_CONNECTED; emit newStatus(Ekos::GUIDE_CONNECTED); } emit newLog(i18n("PHD2: Guiding Started.")); emit newStatus(Ekos::GUIDE_GUIDING); requestExposureTime(); 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: processPHD2State(jsonEvent["State"].toString()); break; case CalibrationFailed: state = CALIBRATION_FAILED; 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: //emit newLog(i18n("PHD2: Looping Exposures.")); break; case LoopingExposuresStopped: emit newLog(i18n("PHD2: Looping Exposures Stopped.")); break; case Settling: case SettleBegin: break; case SettleDone: { bool error = false; if (jsonEvent["Status"].toInt() != 0) { error = true; emit newLog(i18n("PHD2: Settling failed (%1).", jsonEvent["Error"].toString())); } if (state == GUIDING) { if (error) state = STOPPED; } else if (state == DITHERING) { if (error) { state = DITHER_FAILED; //emit ditherFailed(); emit newStatus(GUIDE_DITHERING_ERROR); } else { state = DITHER_SUCCESSFUL; emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); } } } break; case StarSelected: emit newLog(i18n("PHD2: Star Selected.")); break; case StarLost: emit newLog(i18n("PHD2: Star Lost.")); emit newStatus(Ekos::GUIDE_ABORTED); break; case GuidingStopped: emit newLog(i18n("PHD2: Guiding Stopped.")); state = STOPPED; //emit autoGuidingToggled(false); emit newStatus(Ekos::GUIDE_IDLE); break; case Resumed: emit newLog(i18n("PHD2: Guiding Resumed.")); emit newStatus(Ekos::GUIDE_GUIDING); state = GUIDING; break; case GuideStep: { double diff_ra_pixels, diff_de_pixels, diff_ra_arcsecs, diff_de_arcsecs; diff_ra_pixels = jsonEvent["RADistanceRaw"].toDouble(); diff_de_pixels = jsonEvent["DECDistanceRaw"].toDouble(); //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); double total_sqr_RA_error; double total_sqr_DE_error; for(int i=0;iloadFITS(filename, true); - if (imageLoad) - { - guideFrame->updateFrame(); - guideFrame->setTrackingBox(QRect(0,0,width,height)); - emit newStarPixmap(guideFrame->getTrackingBoxPixmap()); - } - -} -void PHD2::setGuideView(FITSView *guideView) -{ - guideFrame = guideView; + case GuideParamChange: + //Don't do anything for now, might change this later. + 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::processPHD2Error(const QJsonObject &jsonError) { QJsonObject jsonErrorObject = jsonError["error"].toObject(); emit newLog(i18n("PHD2 Error: %1", jsonErrorObject["message"].toString())); if (state == DITHERING) { state = DITHER_FAILED; //emit ditherFailed(); emit newStatus(GUIDE_DITHERING_ERROR); if (Options::ditherFailAbortsAutoGuide()) { state = STOPPED; emit newStatus(GUIDE_ABORTED); } else { resume(); } } /* switch (connection) { case CONNECTING: case CONNECTED: emit disconnected(); break; default: break; }*/ } -void PHD2::requestPixelScale(){ - QJsonArray args; - desiredResult=PIXEL_SCALE; - sendJSONRPCRequest("get_pixel_scale", args); -} -void PHD2::requestExposureTime(){ - QJsonArray args; - desiredResult=EXPOSURE_TIME; - sendJSONRPCRequest("get_exposure", args); -} -void PHD2::requestStarImage(int size){ - QJsonArray args2; - args2<loadFITS(filename, true); + if (imageLoad) + { + guideFrame->updateFrame(); + guideFrame->setTrackingBox(QRect(0,0,width,height)); + emit newStarPixmap(guideFrame->getTrackingBoxPixmap()); + } - tcpSocket->write(json_doc.toJson(QJsonDocument::Compact)); - tcpSocket->write("\r\n"); } -void PHD2::setEquipmentConnected(bool enable) +//This method is not handled by PHD2 +bool PHD2::calibrate() { - if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES) + // 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 section handles the methods/requests sent to PHD2, some are not implemented. + +//capture_single_frame + +//clear_calibration +bool PHD2::clearCalibration() +{ + if (connection != EQUIPMENT_CONNECTED) { - setConnectedRetries = 0; - connection = EQUIPMENT_DISCONNECTED; - emit newStatus(Ekos::GUIDE_DISCONNECTED); - return; + emit newLog(i18n("PHD2 Error: Equipment not connected.")); + return false; } - if ((connection == EQUIPMENT_CONNECTED && enable == true) || - (connection == EQUIPMENT_DISCONNECTED && enable == false)) - return; + QJsonArray args; + //This instructs PHD2 which calibration to clear. + args << "mount"; + sendPHD2Request("clear_calibration", args); - if (enable) - connection = EQUIPMENT_CONNECTING; - else - connection = EQUIPMENT_DISCONNECTING; + return true; +} + +//dither +bool PHD2::dither(double pixels) +{ + if (connection != EQUIPMENT_CONNECTED) + { + emit newLog(i18n("PHD2 Error: Equipment not connected.")); + return false; + } QJsonArray args; + QJsonObject settle; - // connected = enable - args << enable; + settle.insert("pixels", static_cast(Options::ditherThreshold())); + settle.insert("time", static_cast(Options::ditherSettle())); + settle.insert("timeout", static_cast(Options::ditherTimeout())); - sendJSONRPCRequest("set_connected", args); - desiredResult=CONNECTION_RESULT; + // Pixels + args << pixels; + // RA Only? + args << false; + // Settle + args << settle; -} + state = DITHERING; + + sendPHD2Request("dither", args); -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; } +//find_star +//flip_calibration +//get_algo_param_names +//get_algo_param +//get_app_state +//get_calibrated +//get_calibration_data +//get_connected +//get_cooler_status +//get_current_equipment +//get_dec_guide_mode + +//get_exposure +void PHD2::requestExposureTime(){ + QJsonArray args; + sendPHD2Request("get_exposure", args); +} + +//get_exposure_durations +//get_lock_position +//get_lock_shift_enabled +//get_lock_shift_params +//get_paused + +//get_pixel_scale +void PHD2::requestPixelScale(){ + QJsonArray args; + sendPHD2Request("get_pixel_scale", args); +} + +//get_profile +//get_profiles +//get_search_region +//get_sensor_temperature + +//get_star_image +void PHD2::requestStarImage(int size){ + QJsonArray args2; + args2<(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(); - sendJSONRPCRequest("guide", args); + sendPHD2Request("guide", args); return true; } -bool PHD2::abort() +//guide_pulse +//loop +//save_image +//set_algo_param + +//set_connected +void PHD2::setEquipmentConnected(bool enable) { - if (connection != EQUIPMENT_CONNECTED) + if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES) { - emit newLog(i18n("PHD2 Error: Equipment not connected.")); - return false; + setConnectedRetries = 0; + connection = EQUIPMENT_DISCONNECTED; + emit newStatus(Ekos::GUIDE_DISCONNECTED); + return; } - sendJSONRPCRequest("stop_capture"); - return true; + if ((connection == EQUIPMENT_CONNECTED && enable == true) || + (connection == EQUIPMENT_DISCONNECTED && enable == false)) + return; + + if (enable) + connection = EQUIPMENT_CONNECTING; + else + connection = EQUIPMENT_DISCONNECTING; + + QJsonArray args; + + // connected = enable + args << enable; + + sendPHD2Request("set_connected", args); + } +//set_dec_guide_mode +//set_exposure +//set_lock_position +//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"; - sendJSONRPCRequest("set_paused", args); + sendPHD2Request("set_paused", args); 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; - sendJSONRPCRequest("set_paused", args); + sendPHD2Request("set_paused", args); return true; } -bool PHD2::dither(double pixels) +//set_profile +//shutdown + +//stop_capture +bool PHD2::abort() { if (connection != EQUIPMENT_CONNECTED) { emit newLog(i18n("PHD2 Error: Equipment not connected.")); return false; } - QJsonArray args; - QJsonObject settle; + sendPHD2Request("stop_capture"); + return true; +} - settle.insert("pixels", static_cast(Options::ditherThreshold())); - settle.insert("time", static_cast(Options::ditherSettle())); - settle.insert("timeout", static_cast(Options::ditherTimeout())); - // Pixels - args << pixels; - // RA Only? - args << false; - // Settle - args << settle; - state = DITHERING; - sendJSONRPCRequest("dither", args); - return true; -} -bool PHD2::clearCalibration() + +//This is how information requests and commands for PHD2 are handled + +void PHD2::sendPHD2Request(const QString &method, const QJsonArray args) { - if (connection != EQUIPMENT_CONNECTED) - { - emit newLog(i18n("PHD2 Error: Equipment not connected.")); - return false; - } + QJsonObject jsonRPC; - QJsonArray args; - //This instructs PHD2 which calibration to clear. - args << "mount"; - sendJSONRPCRequest("clear_calibration", args); + jsonRPC.insert("jsonrpc", "2.0"); + jsonRPC.insert("method", method); - return true; + if (args.empty() == false) + jsonRPC.insert("params", args); + + resultRequests.append(qMakePair(methodID,method)); + jsonRPC.insert("id", methodID++); + + QJsonDocument json_doc(jsonRPC); + + //emit newLog(json_doc.toJson(QJsonDocument::Compact)); + qCDebug(KSTARS_EKOS_GUIDE) << json_doc.toJson(QJsonDocument::Compact); + + tcpSocket->write(json_doc.toJson(QJsonDocument::Compact)); + tcpSocket->write("\r\n"); } } diff --git a/kstars/ekos/guide/externalguide/phd2.h b/kstars/ekos/guide/externalguide/phd2.h index 8604196f9..318755ded 100644 --- a/kstars/ekos/guide/externalguide/phd2.h +++ b/kstars/ekos/guide/externalguide/phd2.h @@ -1,152 +1,160 @@ /* 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 class QTcpSocket; namespace Ekos { /** * @class PHD2 * Uses external PHD2 for guiding. * * @author Jasem Mutlaq * @version 1.1 */ class PHD2 : public GuideInterface { Q_OBJECT public: typedef enum { Version, LockPositionSet, CalibrationComplete, StarSelected, StartGuiding, Paused, StartCalibration, AppState, CalibrationFailed, CalibrationDataFlipped, LoopingExposures, LoopingExposuresStopped, SettleBegin, Settling, SettleDone, StarLost, GuidingStopped, Resumed, GuideStep, GuidingDithered, LockPositionLost, - Alert + Alert, + GuideParamChange } PHD2Event; typedef enum { STOPPED, SELECTED, LOSTLOCK, PAUSED, LOOPING, CALIBRATING, CALIBRATION_FAILED, CALIBRATION_SUCCESSFUL, GUIDING, DITHERING, DITHER_FAILED, DITHER_SUCCESSFUL } PHD2State; typedef enum { DISCONNECTED, CONNECTING, CONNECTED, EQUIPMENT_DISCONNECTING, EQUIPMENT_DISCONNECTED, EQUIPMENT_CONNECTING, EQUIPMENT_CONNECTED } PHD2Connection; typedef enum { PHD2_UNKNOWN, PHD2_RESULT, PHD2_EVENT, PHD2_ERROR, - PHD2_STAR_IMAGE, } PHD2MessageType; typedef enum { NO_RESULT, + CLEAR_CALIBRATION_COMMAND_RECEIVED, + DITHER_COMMAND_RECEIVED, PIXEL_SCALE, + STAR_IMAGE, + GUIDE_COMMAND_RECEIVED, EXPOSURE_TIME, - CONNECTION_RESULT + CONNECTION_RESULT, + SET_PAUSED_COMMAND_RECEIVED, + STOP_CAPTURE_COMMAND_RECEIVED } PHD2ResultType; PHD2(); ~PHD2(); bool Connect() override; bool Disconnect() override; bool isConnected() override { return (connection == CONNECTED || connection == EQUIPMENT_CONNECTED); } bool calibrate() override; bool guide() override; bool abort() override; bool suspend() override; bool resume() override; bool dither(double pixels) override; bool clearCalibration() override; void requestPixelScale(); void requestStarImage(int size); void requestExposureTime(); void setGuideView(FITSView *guideView); private slots: void readPHD2(); void displayError(QAbstractSocket::SocketError socketError); private: void setEquipmentConnected(bool enable); QPointer guideFrame; QVector errorLog; + void sendPHD2Request(const QString &method, const QJsonArray args = QJsonArray()); void sendJSONRPCRequest(const QString &method, const QJsonArray args = QJsonArray()); void processJSON(const QJsonObject &jsonObj, QString rawString); bool blockLine2=false; void processPHD2Event(const QJsonObject &jsonEvent); void processStarImage(const QJsonObject &jsonStarFrame); void processPHD2State(const QString &phd2State); void processPHD2Error(const QJsonObject &jsonError); QTcpSocket *tcpSocket { nullptr }; qint64 methodID { 1 }; QHash events; + QHash methodResults; + QVector> resultRequests; PHD2State state { STOPPED }; PHD2Connection connection { DISCONNECTED }; PHD2Event event { Alert }; - PHD2ResultType desiredResult { NO_RESULT }; uint8_t setConnectedRetries { 0 }; double pixelScale=0; }; }