File indexing completed on 2024-05-12 15:23:36
0001 /* 0002 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "phd2.h" 0008 0009 #include "Options.h" 0010 #include "kspaths.h" 0011 #include "kstars.h" 0012 0013 #include "ekos/manager.h" 0014 #include "fitsviewer/fitsdata.h" 0015 #include "ekos/guide/guide.h" 0016 #include "fitsviewer/fitsview.h" 0017 0018 #include <cassert> 0019 #include <fitsio.h> 0020 #include <KMessageBox> 0021 #include <QImage> 0022 0023 #include <QJsonDocument> 0024 #include <QNetworkReply> 0025 0026 #include <ekos_guide_debug.h> 0027 0028 #define MAX_SET_CONNECTED_RETRIES 3 0029 0030 namespace Ekos 0031 { 0032 PHD2::PHD2() 0033 { 0034 tcpSocket = new QTcpSocket(this); 0035 0036 //This list of available PHD Events is on https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring 0037 0038 events["Version"] = Version; 0039 events["LockPositionSet"] = LockPositionSet; 0040 events["Calibrating"] = Calibrating; 0041 events["CalibrationComplete"] = CalibrationComplete; 0042 events["StarSelected"] = StarSelected; 0043 events["StartGuiding"] = StartGuiding; 0044 events["Paused"] = Paused; 0045 events["StartCalibration"] = StartCalibration; 0046 events["AppState"] = AppState; 0047 events["CalibrationFailed"] = CalibrationFailed; 0048 events["CalibrationDataFlipped"] = CalibrationDataFlipped; 0049 events["LoopingExposures"] = LoopingExposures; 0050 events["LoopingExposuresStopped"] = LoopingExposuresStopped; 0051 events["SettleBegin"] = SettleBegin; 0052 events["Settling"] = Settling; 0053 events["SettleDone"] = SettleDone; 0054 events["StarLost"] = StarLost; 0055 events["GuidingStopped"] = GuidingStopped; 0056 events["Resumed"] = Resumed; 0057 events["GuideStep"] = GuideStep; 0058 events["GuidingDithered"] = GuidingDithered; 0059 events["LockPositionLost"] = LockPositionLost; 0060 events["Alert"] = Alert; 0061 events["GuideParamChange"] = GuideParamChange; 0062 events["ConfigurationChange"] = ConfigurationChange; 0063 0064 //This list of available PHD Methods is on https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring 0065 //Only some of the methods are implemented. The ones that say COMMAND_RECEIVED simply return a 0 saying the command was received. 0066 methodResults["capture_single_frame"] = CAPTURE_SINGLE_FRAME; 0067 methodResults["clear_calibration"] = CLEAR_CALIBRATION_COMMAND_RECEIVED; 0068 methodResults["dither"] = DITHER_COMMAND_RECEIVED; 0069 //find_star 0070 //flip_calibration 0071 //get_algo_param_names 0072 //get_algo_param 0073 methodResults["get_app_state"] = APP_STATE_RECEIVED; 0074 //get_calibrated 0075 //get_calibration_data 0076 methodResults["get_connected"] = IS_EQUIPMENT_CONNECTED; 0077 //get_cooler_status 0078 methodResults["get_current_equipment"] = GET_CURRENT_EQUIPMENT; 0079 methodResults["get_dec_guide_mode"] = DEC_GUIDE_MODE; 0080 methodResults["get_exposure"] = EXPOSURE_TIME; 0081 methodResults["get_exposure_durations"] = EXPOSURE_DURATIONS; 0082 methodResults["get_lock_position"] = LOCK_POSITION; 0083 //get_lock_shift_enabled 0084 //get_lock_shift_params 0085 //get_paused 0086 methodResults["get_pixel_scale"] = PIXEL_SCALE; 0087 //get_profile 0088 //get_profiles 0089 //get_search_region 0090 //get_sensor_temperature 0091 methodResults["get_star_image"] = STAR_IMAGE; 0092 //get_use_subframes 0093 methodResults["guide"] = GUIDE_COMMAND_RECEIVED; 0094 //guide_pulse 0095 methodResults["loop"] = LOOP; 0096 //save_image 0097 //set_algo_param 0098 methodResults["set_connected"] = CONNECTION_RESULT; 0099 methodResults["set_dec_guide_mode"] = SET_DEC_GUIDE_MODE_COMMAND_RECEIVED; 0100 methodResults["set_exposure"] = SET_EXPOSURE_COMMAND_RECEIVED; 0101 methodResults["set_lock_position"] = SET_LOCK_POSITION; 0102 //set_lock_shift_enabled 0103 //set_lock_shift_params 0104 methodResults["set_paused"] = SET_PAUSED_COMMAND_RECEIVED; 0105 //set_profile 0106 //shutdown 0107 methodResults["stop_capture"] = STOP_CAPTURE_COMMAND_RECEIVED; 0108 0109 abortTimer = new QTimer(this); 0110 connect(abortTimer, &QTimer::timeout, this, [ = ] 0111 { 0112 if (state == CALIBRATING) 0113 qCDebug(KSTARS_EKOS_GUIDE) << "Abort timeout expired while calibrating, retrying to guide."; 0114 else if (state == LOSTLOCK) 0115 qCDebug(KSTARS_EKOS_GUIDE) << "Abort timeout expired while reacquiring star, retrying to guide."; 0116 else 0117 qCDebug(KSTARS_EKOS_GUIDE) << "Abort timeout expired, stopping."; 0118 abort(); 0119 }); 0120 0121 ditherTimer = new QTimer(this); 0122 connect(ditherTimer, &QTimer::timeout, this, [ = ] 0123 { 0124 qCDebug(KSTARS_EKOS_GUIDE) << "ditherTimer expired, state" << state << "dithering" << isDitherActive << "settling" << isSettling; 0125 ditherTimer->stop(); 0126 isDitherActive = false; 0127 isSettling = false; 0128 if (Options::ditherFailAbortsAutoGuide()) 0129 { 0130 abort(); 0131 emit newStatus(GUIDE_DITHERING_ERROR); 0132 } 0133 else 0134 { 0135 emit newLog(i18n("PHD2: There was no dithering response from PHD2, but continue guiding.")); 0136 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); 0137 } 0138 }); 0139 0140 stateTimer = new QTimer(this); 0141 connect(stateTimer, &QTimer::timeout, this, [ = ] 0142 { 0143 QTcpSocket::SocketState socketstate = tcpSocket->state(); 0144 switch (socketstate) 0145 { 0146 case QTcpSocket::UnconnectedState: 0147 m_PHD2ReconnectCounter++; 0148 if (m_PHD2ReconnectCounter > PHD2_RECONNECT_THRESHOLD) 0149 { 0150 stateTimer->stop(); 0151 emit newLog(i18n("Giving up reconnecting.")); 0152 } 0153 else 0154 { 0155 emit newLog(i18n("Reconnecting to PHD2 Host: %1, on port %2. . .", Options::pHD2Host(), Options::pHD2Port())); 0156 0157 connect(tcpSocket, &QTcpSocket::readyRead, this, &PHD2::readPHD2, Qt::UniqueConnection); 0158 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 0159 connect(tcpSocket, &QTcpSocket::errorOccurred, this, &PHD2::displayError, Qt::UniqueConnection); 0160 #else 0161 connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, 0162 SLOT(displayError(QAbstractSocket::SocketError))); 0163 #endif 0164 tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port()); 0165 } 0166 break; 0167 case QTcpSocket::ConnectedState: 0168 m_PHD2ReconnectCounter = 0; 0169 checkIfEquipmentConnected(); 0170 requestAppState(); 0171 break; 0172 default: 0173 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: TCP connection state:" << socketstate; 0174 break; 0175 } 0176 }); 0177 } 0178 0179 PHD2::~PHD2() 0180 { 0181 delete abortTimer; 0182 delete ditherTimer; 0183 } 0184 0185 bool PHD2::Connect() 0186 { 0187 switch (connection) 0188 { 0189 case DISCONNECTED: 0190 // Not yet connected, let's connect server 0191 connection = CONNECTING; 0192 emit newLog(i18n("Connecting to PHD2 Host: %1, on port %2. . .", Options::pHD2Host(), Options::pHD2Port())); 0193 0194 connect(tcpSocket, &QTcpSocket::readyRead, this, &PHD2::readPHD2, Qt::UniqueConnection); 0195 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 0196 connect(tcpSocket, &QTcpSocket::errorOccurred, this, &PHD2::displayError, Qt::UniqueConnection); 0197 #else 0198 connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, 0199 SLOT(displayError(QAbstractSocket::SocketError))); 0200 #endif 0201 0202 tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port()); 0203 0204 m_PHD2ReconnectCounter = 0; 0205 stateTimer->start(PHD2_RECONNECT_TIMEOUT); 0206 return true; 0207 0208 case EQUIPMENT_DISCONNECTED: 0209 // Equipment disconnected from PHD2, request reconnection 0210 connectEquipment(true); 0211 return true; 0212 0213 case DISCONNECTING: 0214 // Not supposed to interrupt a running disconnection 0215 return false; 0216 0217 default: 0218 return false; 0219 } 0220 } 0221 0222 void PHD2::ResetConnectionState() 0223 { 0224 connection = DISCONNECTED; 0225 0226 // clear the outstanding and queued RPC requests 0227 pendingRpcResultType = NO_RESULT; 0228 rpcRequestQueue.clear(); 0229 0230 starImageRequested = false; 0231 isSettling = false; 0232 isDitherActive = false; 0233 0234 ditherTimer->stop(); 0235 abortTimer->stop(); 0236 0237 tcpSocket->disconnect(this); 0238 0239 emit newStatus(GUIDE_DISCONNECTED); 0240 } 0241 0242 bool PHD2::Disconnect() 0243 { 0244 switch (connection) 0245 { 0246 case EQUIPMENT_CONNECTED: 0247 emit newLog(i18n("Aborting any capture before disconnecting equipment...")); 0248 abort(); 0249 connection = DISCONNECTING; 0250 break; 0251 0252 case CONNECTED: 0253 case CONNECTING: 0254 case EQUIPMENT_DISCONNECTED: 0255 stateTimer->stop(); 0256 tcpSocket->disconnectFromHost(); 0257 ResetConnectionState(); 0258 if (tcpSocket->state() != QAbstractSocket::UnconnectedState) 0259 tcpSocket->waitForDisconnected(5000); 0260 emit newLog(i18n("Disconnected from PHD2 Host: %1, on port %2.", Options::pHD2Host(), Options::pHD2Port())); 0261 break; 0262 0263 case DISCONNECTING: 0264 case DISCONNECTED: 0265 break; 0266 } 0267 0268 return true; 0269 } 0270 0271 void PHD2::displayError(QAbstractSocket::SocketError socketError) 0272 { 0273 switch (socketError) 0274 { 0275 case QAbstractSocket::RemoteHostClosedError: 0276 emit newLog(i18n("The host disconnected.")); 0277 break; 0278 case QAbstractSocket::HostNotFoundError: 0279 emit newLog(i18n("The host was not found. Please check the host name and port settings in Guide options.")); 0280 break; 0281 case QAbstractSocket::ConnectionRefusedError: 0282 emit newLog(i18n("The connection was refused by the peer. Make sure the PHD2 is running, and check that " 0283 "the host name and port settings are correct.")); 0284 break; 0285 default: 0286 emit newLog(i18n("The following error occurred: %1.", tcpSocket->errorString())); 0287 } 0288 0289 ResetConnectionState(); 0290 0291 emit newStatus(GUIDE_DISCONNECTED); 0292 } 0293 0294 void PHD2::readPHD2() 0295 { 0296 while (!tcpSocket->atEnd() && tcpSocket->canReadLine()) 0297 { 0298 QByteArray line = tcpSocket->readLine(); 0299 if (line.isEmpty()) 0300 continue; 0301 0302 QJsonParseError qjsonError; 0303 0304 QJsonDocument jdoc = QJsonDocument::fromJson(line, &qjsonError); 0305 0306 if (qjsonError.error != QJsonParseError::NoError) 0307 { 0308 emit newLog(i18n("PHD2: invalid response received: %1", QString(line))); 0309 emit newLog(i18n("PHD2: JSON error: %1", qjsonError.errorString())); 0310 continue; 0311 } 0312 0313 QJsonObject jsonObj = jdoc.object(); 0314 0315 if (jsonObj.contains("Event")) 0316 processPHD2Event(jsonObj, line); 0317 else if (jsonObj.contains("error")) 0318 processPHD2Error(jsonObj, line); 0319 else if (jsonObj.contains("result")) 0320 processPHD2Result(jsonObj, line); 0321 } 0322 } 0323 0324 void PHD2::processPHD2Event(const QJsonObject &jsonEvent, const QByteArray &line) 0325 { 0326 if (Options::verboseLogging()) 0327 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: event:" << line; 0328 0329 QString eventName = jsonEvent["Event"].toString(); 0330 0331 if (!events.contains(eventName)) 0332 { 0333 emit newLog(i18n("Unknown PHD2 event: %1", eventName)); 0334 return; 0335 } 0336 0337 event = events.value(eventName); 0338 0339 switch (event) 0340 { 0341 case Version: 0342 emit newLog(i18n("PHD2: Version %1", jsonEvent["PHDVersion"].toString())); 0343 break; 0344 0345 case CalibrationComplete: 0346 emit newLog(i18n("PHD2: Calibration Complete.")); 0347 emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS); 0348 break; 0349 0350 case StartGuiding: 0351 updateGuideParameters(); 0352 requestCurrentEquipmentUpdate(); 0353 // Do not report guiding as started because it will start scheduled capture before guiding is settled 0354 // just print the log message and GUIDE_STARTED status will be set in SettleDone 0355 // phd2 will always send SettleDone event 0356 emit newLog(i18n("PHD2: Waiting for guiding to settle.")); 0357 break; 0358 0359 case Paused: 0360 handlePHD2AppState(PAUSED); 0361 break; 0362 0363 case StartCalibration: 0364 handlePHD2AppState(CALIBRATING); 0365 break; 0366 0367 case AppState: 0368 // AppState is the last of the initial messages received when we first connect to PHD2 0369 processPHD2State(jsonEvent["State"].toString()); 0370 // if the equipment is not already connected, then try to connect it. 0371 if (connection == CONNECTING) 0372 { 0373 emit newLog("PHD2: Connecting equipment and external guider..."); 0374 connectEquipment(true); 0375 } 0376 break; 0377 0378 case CalibrationFailed: 0379 emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString())); 0380 handlePHD2AppState(STOPPED); 0381 break; 0382 0383 case CalibrationDataFlipped: 0384 emit newLog(i18n("Calibration Data Flipped.")); 0385 break; 0386 0387 case LoopingExposures: 0388 handlePHD2AppState(LOOPING); 0389 break; 0390 0391 case LoopingExposuresStopped: 0392 handlePHD2AppState(STOPPED); 0393 break; 0394 0395 case Calibrating: 0396 case Settling: 0397 case SettleBegin: 0398 //This can happen for guiding or for dithering. A Settle done event will arrive when it finishes. 0399 break; 0400 0401 case SettleDone: 0402 { 0403 // guiding stopped during dithering 0404 if (state == PHD2::STOPPED) 0405 return; 0406 0407 bool error = false; 0408 0409 if (jsonEvent["Status"].toInt() != 0) 0410 { 0411 error = true; 0412 emit newLog(i18n("PHD2: Settling failed (%1).", jsonEvent["Error"].toString())); 0413 } 0414 0415 bool wasDithering = isDitherActive; 0416 0417 isDitherActive = false; 0418 isSettling = false; 0419 0420 if (wasDithering) 0421 { 0422 ditherTimer->stop(); 0423 if (error && Options::ditherFailAbortsAutoGuide()) 0424 { 0425 abort(); 0426 emit newStatus(GUIDE_DITHERING_ERROR); 0427 } 0428 else 0429 { 0430 if (error) 0431 emit newLog(i18n("PHD2: There was a dithering error, but continue guiding.")); 0432 0433 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); 0434 } 0435 } 0436 else 0437 { 0438 if (error) 0439 { 0440 emit newLog(i18n("PHD2: Settling failed, aborted.")); 0441 emit newStatus(GUIDE_ABORTED); 0442 } 0443 else 0444 { 0445 // settle completed after "guide" command 0446 emit newLog(i18n("PHD2: Settling complete, Guiding Started.")); 0447 emit newStatus(GUIDE_GUIDING); 0448 } 0449 } 0450 } 0451 break; 0452 0453 case StarSelected: 0454 handlePHD2AppState(SELECTED); 0455 break; 0456 0457 case StarLost: 0458 // If we lost the guide star, let the state and abort timers update our state 0459 handlePHD2AppState(LOSTLOCK); 0460 break; 0461 0462 case GuidingStopped: 0463 handlePHD2AppState(STOPPED); 0464 break; 0465 0466 case Resumed: 0467 handlePHD2AppState(GUIDING); 0468 break; 0469 0470 case GuideStep: 0471 { 0472 // If we lost the guide star, let the state timer update our state 0473 // Sometimes PHD2 is actually not guiding at that time, so we'll either resume or abort 0474 if (state == LOSTLOCK) 0475 emit newLog(i18n("PHD2: Star found, guiding is resuming...")); 0476 0477 if (isDitherActive) 0478 return; 0479 0480 double diff_ra_pixels, diff_de_pixels, diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec, snr; 0481 QString RADirection, DECDirection; 0482 diff_ra_pixels = jsonEvent["RADistanceRaw"].toDouble(); 0483 diff_de_pixels = jsonEvent["DECDistanceRaw"].toDouble(); 0484 pulse_ra = jsonEvent["RADuration"].toDouble(); 0485 pulse_dec = jsonEvent["DECDuration"].toDouble(); 0486 RADirection = jsonEvent["RADirection"].toString(); 0487 DECDirection = jsonEvent["DECDirection"].toString(); 0488 snr = jsonEvent["SNR"].toDouble(); 0489 0490 if (RADirection == "East") 0491 pulse_ra = -pulse_ra; //West Direction is Positive, East is Negative 0492 if (DECDirection == "South") 0493 pulse_dec = -pulse_dec; //South Direction is Negative, North is Positive 0494 0495 //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. 0496 if (pixelScale != 0) 0497 { 0498 diff_ra_arcsecs = diff_ra_pixels * pixelScale; 0499 diff_de_arcsecs = diff_de_pixels * pixelScale; 0500 } 0501 else 0502 { 0503 diff_ra_arcsecs = 206.26480624709 * diff_ra_pixels * ccdPixelSizeX / mountFocalLength; 0504 diff_de_arcsecs = 206.26480624709 * diff_de_pixels * ccdPixelSizeY / mountFocalLength; 0505 } 0506 0507 if (std::isfinite(snr)) 0508 emit newSNR(snr); 0509 0510 if (std::isfinite(diff_ra_arcsecs) && std::isfinite(diff_de_arcsecs)) 0511 { 0512 errorLog.append(QPointF(diff_ra_arcsecs, diff_de_arcsecs)); 0513 if(errorLog.size() > 50) 0514 errorLog.remove(0); 0515 0516 emit newAxisDelta(diff_ra_arcsecs, diff_de_arcsecs); 0517 emit newAxisPulse(pulse_ra, pulse_dec); 0518 0519 // Does PHD2 real a sky background or num-stars measure? 0520 emit guideStats(diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec, 0521 std::isfinite(snr) ? snr : 0, 0, 0); 0522 0523 double total_sqr_RA_error = 0.0; 0524 double total_sqr_DE_error = 0.0; 0525 0526 for (auto &point : errorLog) 0527 { 0528 total_sqr_RA_error += point.x() * point.x(); 0529 total_sqr_DE_error += point.y() * point.y(); 0530 } 0531 0532 emit newAxisSigma(sqrt(total_sqr_RA_error / errorLog.size()), sqrt(total_sqr_DE_error / errorLog.size())); 0533 0534 } 0535 //Note that if it is receiving full size remote images, it should not get the guide star image. 0536 //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 0537 //If we are getting the full size image, we will want to know the lock position for the image that loads in the viewer. 0538 if ( Options::guideSubframe() || currentCameraIsNotInEkos ) 0539 requestStarImage(32); //This requests a star image for the guide view. 32 x 32 pixels 0540 else 0541 requestLockPosition(); 0542 } 0543 break; 0544 0545 case GuidingDithered: 0546 break; 0547 0548 case LockPositionSet: 0549 handlePHD2AppState(SELECTED); 0550 break; 0551 0552 case LockPositionLost: 0553 handlePHD2AppState(LOSTLOCK); 0554 break; 0555 0556 case Alert: 0557 emit newLog(i18n("PHD2 %1: %2", jsonEvent["Type"].toString(), jsonEvent["Msg"].toString())); 0558 break; 0559 0560 case GuideParamChange: 0561 case ConfigurationChange: 0562 //Don't do anything for now, might change this later. 0563 //Some Possible Parameter Names: 0564 //Backlash comp enabled, Backlash comp amount, 0565 //For Each Axis: MinMove, Max Duration, 0566 //PPEC aggressiveness, PPEC prediction weight, 0567 //Resist switch minimum motion, Resist switch aggression, 0568 //Low-pass minimum move, Low-pass slope weight, 0569 //Low-pass2 minimum move, Low-pass2 aggressiveness, 0570 //Hysteresis hysteresis, Hysteresis aggression 0571 break; 0572 0573 } 0574 } 0575 0576 void PHD2::processPHD2State(const QString &phd2State) 0577 { 0578 if (phd2State == "Stopped") 0579 handlePHD2AppState(STOPPED); 0580 else if (phd2State == "Selected") 0581 handlePHD2AppState(SELECTED); 0582 else if (phd2State == "Calibrating") 0583 handlePHD2AppState(CALIBRATING); 0584 else if (phd2State == "Guiding") 0585 handlePHD2AppState(GUIDING); 0586 else if (phd2State == "LostLock") 0587 handlePHD2AppState(LOSTLOCK); 0588 else if (phd2State == "Paused") 0589 handlePHD2AppState(PAUSED); 0590 else if (phd2State == "Looping") 0591 handlePHD2AppState(LOOPING); 0592 else emit newLog(QString("PHD2: Unsupported app state ") + phd2State + "."); 0593 } 0594 0595 void PHD2::handlePHD2AppState(PHD2State newstate) 0596 { 0597 // do not handle the same state twice 0598 if (state == newstate) 0599 return; 0600 0601 switch (newstate) 0602 { 0603 case STOPPED: 0604 switch (state) 0605 { 0606 case CALIBRATING: 0607 //emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString())); 0608 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); 0609 break; 0610 case LOOPING: 0611 emit newLog(i18n("PHD2: Looping Exposures Stopped.")); 0612 emit newStatus(Ekos::GUIDE_IDLE); 0613 break; 0614 case GUIDING: 0615 case LOSTLOCK: 0616 emit newLog(i18n("PHD2: Guiding Stopped.")); 0617 emit newStatus(Ekos::GUIDE_ABORTED); 0618 break; 0619 default: 0620 if (connection == DISCONNECTING) 0621 { 0622 emit newLog("PHD2: Disconnecting equipment and external guider..."); 0623 connectEquipment(false); 0624 } 0625 break; 0626 } 0627 break; 0628 0629 case SELECTED: 0630 switch (state) 0631 { 0632 case STOPPED: 0633 case CALIBRATING: 0634 case GUIDING: 0635 emit newLog(i18n("PHD2: Lock Position Set.")); 0636 if (isSettling) 0637 { 0638 newstate = CALIBRATING; 0639 emit newStatus(Ekos::GUIDE_CALIBRATING); 0640 } 0641 break; 0642 case DITHERING: 0643 // do nothing, this is the initial step in PHD2 for dithering 0644 break; 0645 default: 0646 emit newLog(i18n("PHD2: Star Selected.")); 0647 emit newStatus(GUIDE_STAR_SELECT); 0648 } 0649 break; 0650 0651 case GUIDING: 0652 switch (state) 0653 { 0654 case PAUSED: 0655 case DITHERING: 0656 emit newLog(i18n("PHD2: Dithering successful.")); 0657 abortTimer->stop(); 0658 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); 0659 break; 0660 default: 0661 emit newLog(i18n("PHD2: Guiding started.")); 0662 abortTimer->stop(); 0663 emit newStatus(Ekos::GUIDE_GUIDING); 0664 break; 0665 } 0666 break; 0667 0668 case LOSTLOCK: 0669 switch (state) 0670 { 0671 case CALIBRATING: 0672 emit newLog(i18n("PHD2: Lock Position Lost, continuing calibration.")); 0673 // Don't be paranoid, accept star-lost events during calibration and trust PHD2 to complete 0674 //newstate = STOPPED; 0675 //emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); 0676 break; 0677 case GUIDING: 0678 emit newLog(i18n("PHD2: Star Lost. Trying to reacquire for %1s.", Options::guideLostStarTimeout())); 0679 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000); 0680 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout started (" << Options::guideLostStarTimeout() << " sec)"; 0681 emit newStatus(Ekos::GUIDE_REACQUIRE); 0682 break; 0683 default: 0684 emit newLog(i18n("PHD2: Lock Position Lost.")); 0685 break; 0686 } 0687 break; 0688 0689 case PAUSED: 0690 emit newLog(i18n("PHD2: Guiding paused.")); 0691 emit newStatus(GUIDE_SUSPENDED); 0692 break; 0693 0694 case CALIBRATING: 0695 emit newLog(i18n("PHD2: Calibrating, timing out in %1s.", Options::guideCalibrationTimeout())); 0696 abortTimer->start(static_cast<int>(Options::guideCalibrationTimeout()) * 1000); 0697 emit newStatus(GUIDE_CALIBRATING); 0698 break; 0699 0700 case LOOPING: 0701 switch (state) 0702 { 0703 case CALIBRATING: 0704 emit newLog(i18n("PHD2: Calibration turned to looping, failed.")); 0705 emit newStatus(GUIDE_CALIBRATION_ERROR); 0706 break; 0707 default: 0708 emit newLog(i18n("PHD2: Looping Exposures.")); 0709 emit newStatus(GUIDE_LOOPING); 0710 break; 0711 } 0712 break; 0713 case DITHERING: 0714 emit newLog(i18n("PHD2: Dithering started.")); 0715 emit newStatus(GUIDE_DITHERING); 0716 break; 0717 } 0718 0719 state = newstate; 0720 } 0721 0722 void PHD2::processPHD2Result(const QJsonObject &jsonObj, const QByteArray &line) 0723 { 0724 PHD2ResultType resultType = takeRequestFromList(jsonObj); 0725 0726 if (resultType == STAR_IMAGE) 0727 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received star image response, id" << 0728 jsonObj["id"].toInt(); // don't spam the log with image data 0729 else 0730 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: response:" << line; 0731 0732 switch (resultType) 0733 { 0734 case NO_RESULT: 0735 //Ekos didn't ask for this result? 0736 break; 0737 0738 case CAPTURE_SINGLE_FRAME: //capture_single_frame 0739 break; 0740 0741 case CLEAR_CALIBRATION_COMMAND_RECEIVED: //clear_calibration 0742 emit newLog(i18n("PHD2: Calibration is cleared")); 0743 break; 0744 0745 case DITHER_COMMAND_RECEIVED: //dither 0746 handlePHD2AppState(DITHERING); 0747 break; 0748 0749 //find_star 0750 //flip_calibration 0751 //get_algo_param_names 0752 //get_algo_param 0753 0754 case APP_STATE_RECEIVED: //get_app_state 0755 { 0756 QString state = jsonObj["State"].toString(); 0757 if (state.isEmpty()) 0758 state = jsonObj["result"].toString(); 0759 if (state.isEmpty()) 0760 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received unsupported app state"; 0761 else 0762 processPHD2State(state); 0763 } 0764 break; 0765 0766 //get_calibrated 0767 //get_calibration_data 0768 0769 case IS_EQUIPMENT_CONNECTED: //get_connected 0770 { 0771 bool isConnected = jsonObj["result"].toBool(); 0772 switch (connection) 0773 { 0774 case CONNECTING: 0775 // We just plugged in server, request equipment connection if needed 0776 if (isConnected) 0777 { 0778 connection = CONNECTED; 0779 setEquipmentConnected(); 0780 } 0781 else connectEquipment(true); 0782 break; 0783 0784 case CONNECTED: 0785 // We were waiting for equipment to be connected after plugging in server 0786 if (isConnected) 0787 setEquipmentConnected(); 0788 break; 0789 0790 case DISCONNECTING: 0791 // We were waiting for equipment to be disconnected before unplugging from server 0792 if (!isConnected) 0793 { 0794 connection = EQUIPMENT_DISCONNECTED; 0795 Disconnect(); 0796 } 0797 else connectEquipment(false); 0798 break; 0799 0800 case EQUIPMENT_CONNECTED: 0801 // Equipment was disconnected from PHD2 side, so notify clients and wait. 0802 if (!isConnected) 0803 { 0804 // TODO: setEquipmentDisconnected() 0805 connection = EQUIPMENT_DISCONNECTED; 0806 emit newStatus(Ekos::GUIDE_DISCONNECTED); 0807 } 0808 break; 0809 0810 case DISCONNECTED: 0811 case EQUIPMENT_DISCONNECTED: 0812 // Equipment was connected from PHD2 side, so notify clients and wait. 0813 if (isConnected) 0814 setEquipmentConnected(); 0815 break; 0816 } 0817 } 0818 break; 0819 0820 //get_cooler_status 0821 case GET_CURRENT_EQUIPMENT: //get_current_equipment 0822 { 0823 QJsonObject equipObject = jsonObj["result"].toObject(); 0824 currentCamera = equipObject["camera"].toObject()["name"].toString(); 0825 currentMount = equipObject["mount"].toObject()["name"].toString(); 0826 currentAuxMount = equipObject["aux_mount"].toObject()["name"].toString(); 0827 0828 emit guideEquipmentUpdated(); 0829 0830 break; 0831 } 0832 0833 0834 case DEC_GUIDE_MODE: //get_dec_guide_mode 0835 { 0836 QString mode = jsonObj["result"].toString(); 0837 Ekos::Manager::Instance()->guideModule()->updateDirectionsFromPHD2(mode); 0838 emit newLog(i18n("PHD2: DEC Guide Mode is Set to: %1", mode)); 0839 } 0840 break; 0841 0842 0843 case EXPOSURE_TIME: //get_exposure 0844 { 0845 int exposurems = jsonObj["result"].toInt(); 0846 double exposureTime = exposurems / 1000.0; 0847 Ekos::Manager::Instance()->guideModule()->setExposure(exposureTime); 0848 emit newLog(i18n("PHD2: Exposure Time set to: ") + QString::number(exposureTime, 'f', 2)); 0849 break; 0850 } 0851 0852 0853 case EXPOSURE_DURATIONS: //get_exposure_durations 0854 { 0855 QVariantList exposureListArray = jsonObj["result"].toArray().toVariantList(); 0856 logValidExposureTimes = i18n("PHD2: Valid Exposure Times: Auto, "); 0857 QList<double> values; 0858 for(int i = 1; i < exposureListArray.size(); 0859 i ++) //For some reason PHD2 has a negative exposure time of 1 at the start of the array? 0860 values << exposureListArray.at(i).toDouble() / 1000.0; //PHD2 reports in ms. 0861 logValidExposureTimes += Ekos::Manager::Instance()->guideModule()->setRecommendedExposureValues(values); 0862 emit newLog(logValidExposureTimes); 0863 break; 0864 } 0865 case LOCK_POSITION: //get_lock_position 0866 { 0867 if(jsonObj["result"].toArray().count() == 2) 0868 { 0869 double x = jsonObj["result"].toArray().at(0).toDouble(); 0870 double y = jsonObj["result"].toArray().at(1).toDouble(); 0871 QVector3D newStarCenter(x, y, 0); 0872 emit newStarPosition(newStarCenter, true); 0873 0874 //This is needed so that PHD2 sends the new star pixmap when 0875 //remote images are enabled. 0876 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap()); 0877 } 0878 break; 0879 } 0880 //get_lock_shift_enabled 0881 //get_lock_shift_params 0882 //get_paused 0883 0884 case PIXEL_SCALE: //get_pixel_scale 0885 pixelScale = jsonObj["result"].toDouble(); 0886 if (pixelScale == 0) 0887 emit newLog(i18n("PHD2: Please set CCD and telescope parameters in PHD2, Pixel Scale is invalid.")); 0888 else 0889 emit newLog(i18n("PHD2: Pixel Scale is %1 arcsec per pixel", QString::number(pixelScale, 'f', 2))); 0890 break; 0891 0892 //get_profile 0893 //get_profiles 0894 //get_search_region 0895 //get_sensor_temperature 0896 0897 case STAR_IMAGE: //get_star_image 0898 { 0899 starImageRequested = false; 0900 QJsonObject jsonResult = jsonObj["result"].toObject(); 0901 processStarImage(jsonResult); 0902 break; 0903 } 0904 0905 //get_use_subframes 0906 0907 case GUIDE_COMMAND_RECEIVED: //guide 0908 if (0 != jsonObj["result"].toInt(0)) 0909 { 0910 emit newLog("PHD2: Guide command was rejected."); 0911 handlePHD2AppState(STOPPED); 0912 } 0913 break; 0914 0915 //guide_pulse 0916 0917 case LOOP: //loop 0918 handlePHD2AppState(jsonObj["result"].toBool() ? LOOPING : STOPPED); 0919 break; 0920 0921 //save_image 0922 //set_algo_param 0923 0924 case CONNECTION_RESULT: //set_connected 0925 checkIfEquipmentConnected(); 0926 break; 0927 0928 case SET_DEC_GUIDE_MODE_COMMAND_RECEIVED: //set_dec_guide_mode 0929 checkDEGuideMode(); 0930 break; 0931 0932 case SET_EXPOSURE_COMMAND_RECEIVED: //set_exposure 0933 requestExposureTime(); //This will check what it was set to and print a message as to what it is. 0934 break; 0935 0936 case SET_LOCK_POSITION: //set_lock_position 0937 handlePHD2AppState(SELECTED); 0938 break; 0939 0940 //set_lock_shift_enabled 0941 //set_lock_shift_params 0942 0943 case SET_PAUSED_COMMAND_RECEIVED: //set_paused 0944 handlePHD2AppState(PAUSED); 0945 break; 0946 //set_profile 0947 //shutdown 0948 0949 case STOP_CAPTURE_COMMAND_RECEIVED: //stop_capture 0950 handlePHD2AppState(STOPPED); 0951 //emit newStatus(GUIDE_ABORTED); 0952 break; 0953 } 0954 0955 // send the next pending call 0956 sendNextRpcCall(); 0957 } 0958 0959 void PHD2::processPHD2Error(const QJsonObject &jsonError, const QByteArray &line) 0960 { 0961 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: error:" << line; 0962 0963 QJsonObject jsonErrorObject = jsonError["error"].toObject(); 0964 0965 PHD2ResultType resultType = takeRequestFromList(jsonError); 0966 0967 // This means the user mistakenly entered an invalid exposure time. 0968 switch (resultType) 0969 { 0970 case SET_EXPOSURE_COMMAND_RECEIVED: 0971 emit newLog(logValidExposureTimes); //This will let the user know the valid exposure durations 0972 QTimer::singleShot(300, [ = ] {requestExposureTime();}); //This will reset the Exposure time in Ekos to PHD2's current exposure time after a third of a second. 0973 break; 0974 0975 case CONNECTION_RESULT: 0976 connection = EQUIPMENT_DISCONNECTED; 0977 emit newStatus(Ekos::GUIDE_DISCONNECTED); 0978 break; 0979 0980 case DITHER_COMMAND_RECEIVED: 0981 ditherTimer->stop(); 0982 isSettling = false; 0983 isDitherActive = false; 0984 emit newStatus(GUIDE_DITHERING_ERROR); 0985 0986 if (Options::ditherFailAbortsAutoGuide()) 0987 { 0988 abort(); 0989 emit newLog("PHD2: failing after dithering aborts."); 0990 emit newStatus(GUIDE_ABORTED); 0991 } 0992 else 0993 { 0994 // !FIXME-ag why is this trying to resume (un-pause)? 0995 resume(); 0996 } 0997 break; 0998 0999 case GUIDE_COMMAND_RECEIVED: 1000 isSettling = false; 1001 break; 1002 1003 default: 1004 emit newLog(i18n("PHD2 Error: unhandled '%1'", jsonErrorObject["message"].toString())); 1005 break; 1006 } 1007 1008 // send the next pending call 1009 sendNextRpcCall(); 1010 } 1011 1012 //These methods process the Star Images the PHD2 provides 1013 1014 void PHD2::setGuideView(const QSharedPointer<FITSView> &guideView) 1015 { 1016 m_GuideFrame = guideView; 1017 } 1018 1019 void PHD2::processStarImage(const QJsonObject &jsonStarFrame) 1020 { 1021 //The width and height of the received PHD2 Star Image 1022 int width = jsonStarFrame["width"].toInt(); 1023 int height = jsonStarFrame["height"].toInt(); 1024 1025 //This section sets up the FITS File 1026 fitsfile *fptr = nullptr; 1027 int status = 0; 1028 long fpixel = 1, naxis = 2, nelements, exposure; 1029 long naxes[2] = { width, height }; 1030 char error_status[512] = {0}; 1031 1032 void* fits_buffer = nullptr; 1033 size_t fits_buffer_size = 0; 1034 if (fits_create_memfile(&fptr, &fits_buffer, &fits_buffer_size, 4096, realloc, &status)) 1035 { 1036 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_file failed:" << error_status; 1037 return; 1038 } 1039 1040 if (fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status)) 1041 { 1042 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_img failed:" << error_status; 1043 status = 0; 1044 fits_close_file(fptr, &status); 1045 free(fits_buffer); 1046 return; 1047 } 1048 1049 //Note, this is made up. If you want the actual exposure time, you have to request it from PHD2 1050 exposure = 1; 1051 fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status); 1052 1053 //This section takes the Pixels from the JSON Document 1054 //Then it converts from base64 to a QByteArray 1055 //Then it creates a datastream from the QByteArray to the pixel array for the FITS File 1056 QByteArray converted = QByteArray::fromBase64(jsonStarFrame["pixels"].toString().toLocal8Bit()); 1057 1058 //This finishes up and closes the FITS file 1059 nelements = naxes[0] * naxes[1]; 1060 if (fits_write_img(fptr, TUSHORT, fpixel, nelements, converted.data(), &status)) 1061 { 1062 fits_get_errstatus(status, error_status); 1063 qCWarning(KSTARS_EKOS_GUIDE) << "fits_write_img failed:" << error_status; 1064 status = 0; 1065 fits_close_file(fptr, &status); 1066 free(fits_buffer); 1067 return; 1068 } 1069 1070 if (fits_flush_file(fptr, &status)) 1071 { 1072 fits_get_errstatus(status, error_status); 1073 qCWarning(KSTARS_EKOS_GUIDE) << "fits_flush_file failed:" << error_status; 1074 status = 0; 1075 fits_close_file(fptr, &status); 1076 free(fits_buffer); 1077 return; 1078 } 1079 1080 if (fits_close_file(fptr, &status)) 1081 { 1082 fits_get_errstatus(status, error_status); 1083 qCWarning(KSTARS_EKOS_GUIDE) << "fits_close_file failed:" << error_status; 1084 free(fits_buffer); 1085 return; 1086 } 1087 1088 //This loads the FITS file in the Guide FITSView 1089 //Then it updates the Summary Screen 1090 QSharedPointer<FITSData> fdata; 1091 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(fits_buffer), fits_buffer_size); 1092 fdata.reset(new FITSData(), &QObject::deleteLater); 1093 fdata->loadFromBuffer(buffer, "fits"); 1094 free(fits_buffer); 1095 m_GuideFrame->loadData(fdata); 1096 1097 m_GuideFrame->updateFrame(); 1098 m_GuideFrame->setTrackingBox(QRect(0, 0, width, height)); 1099 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap()); 1100 } 1101 1102 void PHD2::setEquipmentConnected() 1103 { 1104 if (connection != EQUIPMENT_CONNECTED) 1105 { 1106 setConnectedRetries = 0; 1107 connection = EQUIPMENT_CONNECTED; 1108 emit newStatus(Ekos::GUIDE_CONNECTED); 1109 updateGuideParameters(); 1110 requestExposureDurations(); 1111 requestCurrentEquipmentUpdate(); 1112 } 1113 } 1114 1115 void PHD2::updateGuideParameters() 1116 { 1117 if (pixelScale == 0) 1118 requestPixelScale(); 1119 requestExposureTime(); 1120 checkDEGuideMode(); 1121 } 1122 1123 //This section handles the methods/requests sent to PHD2, some are not implemented. 1124 1125 //capture_single_frame 1126 void PHD2::captureSingleFrame() 1127 { 1128 sendPHD2Request("capture_single_frame"); 1129 } 1130 1131 //clear_calibration 1132 bool PHD2::clearCalibration() 1133 { 1134 if (connection != EQUIPMENT_CONNECTED) 1135 { 1136 emit newLog(i18n("PHD2 Error: Equipment not connected.")); 1137 emit newStatus(Ekos::GUIDE_ABORTED); 1138 return false; 1139 } 1140 1141 QJsonArray args; 1142 //This instructs PHD2 which calibration to clear. 1143 args << "mount"; 1144 sendPHD2Request("clear_calibration", args); 1145 1146 return true; 1147 } 1148 1149 //dither 1150 bool PHD2::dither(double pixels) 1151 { 1152 if (connection != EQUIPMENT_CONNECTED) 1153 { 1154 emit newLog(i18n("PHD2 Error: Equipment not connected.")); 1155 emit newStatus(Ekos::GUIDE_ABORTED); 1156 return false; 1157 } 1158 1159 if (isSettling) 1160 { 1161 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring dither requested while already settling"; 1162 1163 if (!isDitherActive) 1164 { 1165 // act like we just dithered so we get the appropriate 1166 // effects after the settling completes 1167 handlePHD2AppState(DITHERING); 1168 isDitherActive = true; 1169 } 1170 return true; 1171 } 1172 1173 QJsonArray args; 1174 QJsonObject settle; 1175 1176 int ditherTimeout = static_cast<int>(Options::ditherTimeout()); 1177 1178 settle.insert("pixels", static_cast<double>(Options::ditherThreshold())); 1179 settle.insert("time", static_cast<int>(Options::ditherSettle())); 1180 settle.insert("timeout", ditherTimeout); 1181 1182 // Pixels 1183 args << pixels; 1184 // RA Only? 1185 args << false; 1186 // Settle 1187 args << settle; 1188 1189 isSettling = true; 1190 isDitherActive = true; 1191 1192 // PHD2 will send a SettleDone event shortly after the settling 1193 // timeout in PHD2. We don't really need a timer here, but we'll 1194 // set one anyway (belt and suspenders). Make sure to give an 1195 // extra time allowance since PHD2 won't report its timeout until 1196 // the completion of the next guide exposure after the timeout 1197 // period expires. 1198 enum { TIMEOUT_EXTRA_SECONDS = 60 }; // at least as long as any reasonable guide exposure 1199 int millis = (ditherTimeout + TIMEOUT_EXTRA_SECONDS) * 1000; 1200 ditherTimer->start(millis); 1201 1202 sendPHD2Request("dither", args); 1203 1204 handlePHD2AppState(DITHERING); 1205 1206 return true; 1207 } 1208 1209 //find_star 1210 //flip_calibration 1211 //get_algo_param_names 1212 //get_algo_param 1213 1214 //get_app_state 1215 void PHD2::requestAppState() 1216 { 1217 sendPHD2Request("get_app_state"); 1218 } 1219 1220 //get_calibrated 1221 //get_calibration_data 1222 1223 //get_connected 1224 void PHD2::checkIfEquipmentConnected() 1225 { 1226 sendPHD2Request("get_connected"); 1227 } 1228 1229 //get_cooler_status 1230 //get_current_equipment 1231 void PHD2::requestCurrentEquipmentUpdate() 1232 { 1233 sendPHD2Request("get_current_equipment"); 1234 } 1235 1236 //get_dec_guide_mode 1237 void PHD2::checkDEGuideMode() 1238 { 1239 sendPHD2Request("get_dec_guide_mode"); 1240 } 1241 1242 //get_exposure 1243 void PHD2::requestExposureTime() 1244 { 1245 sendPHD2Request("get_exposure"); 1246 } 1247 1248 //get_exposure_durations 1249 void PHD2::requestExposureDurations() 1250 { 1251 sendPHD2Request("get_exposure_durations"); 1252 } 1253 1254 //get_lock_position 1255 void PHD2::requestLockPosition() 1256 { 1257 sendPHD2Request("get_lock_position"); 1258 } 1259 //get_lock_shift_enabled 1260 //get_lock_shift_params 1261 //get_paused 1262 1263 //get_pixel_scale 1264 void PHD2::requestPixelScale() 1265 { 1266 sendPHD2Request("get_pixel_scale"); 1267 } 1268 1269 //get_profile 1270 //get_profiles 1271 //get_search_region 1272 //get_sensor_temperature 1273 1274 //get_star_image 1275 void PHD2::requestStarImage(int size) 1276 { 1277 if (starImageRequested) 1278 { 1279 if (Options::verboseLogging()) 1280 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: skip extra star image request"; 1281 return; 1282 } 1283 1284 QJsonArray args2; 1285 args2 << size; // This is both the width and height. 1286 sendPHD2Request("get_star_image", args2); 1287 1288 starImageRequested = true; 1289 } 1290 1291 //get_use_subframes 1292 1293 //guide 1294 bool PHD2::guide() 1295 { 1296 if (state == GUIDING) 1297 { 1298 emit newLog(i18n("PHD2: Guiding is already running.")); 1299 emit newStatus(Ekos::GUIDE_GUIDING); 1300 return true; 1301 } 1302 1303 if (connection != EQUIPMENT_CONNECTED) 1304 { 1305 emit newLog(i18n("PHD2 Error: Equipment not connected.")); 1306 emit newStatus(Ekos::GUIDE_ABORTED); 1307 return false; 1308 } 1309 1310 QJsonArray args; 1311 QJsonObject settle; 1312 1313 settle.insert("pixels", static_cast<double>(Options::ditherThreshold())); 1314 settle.insert("time", static_cast<int>(Options::ditherSettle())); 1315 settle.insert("timeout", static_cast<int>(Options::ditherTimeout())); 1316 1317 // Settle param 1318 args << settle; 1319 // Recalibrate param 1320 args << false; 1321 1322 errorLog.clear(); 1323 1324 isSettling = true; 1325 sendPHD2Request("guide", args); 1326 1327 return true; 1328 } 1329 1330 //guide_pulse 1331 //loop 1332 void PHD2::loop() 1333 { 1334 sendPHD2Request("loop"); 1335 } 1336 //save_image 1337 //set_algo_param 1338 1339 //set_connected 1340 void PHD2::connectEquipment(bool enable) 1341 { 1342 if (connection == EQUIPMENT_CONNECTED && enable == true) 1343 return; 1344 1345 if (connection == EQUIPMENT_DISCONNECTED && enable == false) 1346 return; 1347 1348 if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES) 1349 { 1350 setConnectedRetries = 0; 1351 connection = EQUIPMENT_DISCONNECTED; 1352 emit newStatus(Ekos::GUIDE_DISCONNECTED); 1353 return; 1354 } 1355 1356 pixelScale = 0 ; 1357 1358 QJsonArray args; 1359 1360 // connected = enable 1361 args << enable; 1362 1363 if (enable) 1364 emit newLog(i18n("PHD2: Connecting Equipment. . .")); 1365 else 1366 emit newLog(i18n("PHD2: Disconnecting Equipment. . .")); 1367 1368 sendPHD2Request("set_connected", args); 1369 } 1370 1371 //set_dec_guide_mode 1372 void PHD2::requestSetDEGuideMode(bool deEnabled, bool nEnabled, 1373 bool sEnabled) //Possible Settings Off, Auto, North, and South 1374 { 1375 QJsonArray args; 1376 1377 if(deEnabled) 1378 { 1379 if(nEnabled && sEnabled) 1380 args << "Auto"; 1381 else if(nEnabled) 1382 args << "North"; 1383 else if(sEnabled) 1384 args << "South"; 1385 else 1386 args << "Off"; 1387 } 1388 else 1389 { 1390 args << "Off"; 1391 } 1392 1393 sendPHD2Request("set_dec_guide_mode", args); 1394 } 1395 1396 //set_exposure 1397 void PHD2::requestSetExposureTime(int time) //Note: time is in milliseconds 1398 { 1399 QJsonArray args; 1400 args << time; 1401 sendPHD2Request("set_exposure", args); 1402 } 1403 1404 //set_lock_position 1405 void PHD2::setLockPosition(double x, double y) 1406 { 1407 // Note: false will mean if a guide star is near the coordinates, it will use that. 1408 QJsonArray args; 1409 args << x << y << false; 1410 sendPHD2Request("set_lock_position", args); 1411 } 1412 //set_lock_shift_enabled 1413 //set_lock_shift_params 1414 1415 //set_paused 1416 bool PHD2::suspend() 1417 { 1418 if (connection != EQUIPMENT_CONNECTED) 1419 { 1420 emit newLog(i18n("PHD2 Error: Equipment not connected.")); 1421 emit newStatus(Ekos::GUIDE_ABORTED); 1422 return false; 1423 } 1424 1425 QJsonArray args; 1426 1427 // Paused param 1428 args << true; 1429 // FULL param 1430 args << "full"; 1431 1432 sendPHD2Request("set_paused", args); 1433 1434 if (abortTimer->isActive()) 1435 { 1436 // Avoid that the a preceding lost star event leads to an abort while guiding is suspended. 1437 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout cancelled."; 1438 abortTimer->stop(); 1439 } 1440 1441 return true; 1442 } 1443 1444 //set_paused (also) 1445 bool PHD2::resume() 1446 { 1447 if (connection != EQUIPMENT_CONNECTED) 1448 { 1449 emit newLog(i18n("PHD2 Error: Equipment not connected.")); 1450 emit newStatus(Ekos::GUIDE_ABORTED); 1451 return false; 1452 } 1453 1454 QJsonArray args; 1455 1456 // Paused param 1457 args << false; 1458 1459 sendPHD2Request("set_paused", args); 1460 1461 if (state == LOSTLOCK) 1462 { 1463 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout restarted."; 1464 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000); 1465 } 1466 1467 return true; 1468 } 1469 1470 //set_profile 1471 //shutdown 1472 1473 //stop_capture 1474 bool PHD2::abort() 1475 { 1476 if (connection != EQUIPMENT_CONNECTED) 1477 { 1478 emit newLog(i18n("PHD2 Error: Equipment not connected.")); 1479 emit newStatus(Ekos::GUIDE_ABORTED); 1480 return false; 1481 } 1482 1483 abortTimer->stop(); 1484 1485 sendPHD2Request("stop_capture"); 1486 return true; 1487 } 1488 1489 //This method is not handled by PHD2 1490 bool PHD2::calibrate() 1491 { 1492 // We don't explicitly do calibration since it is done in the guide step by PHD2 anyway 1493 //emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS); 1494 return true; 1495 } 1496 1497 //This is how information requests and commands for PHD2 are handled 1498 1499 void PHD2::sendRpcCall(QJsonObject &call, PHD2ResultType resultType) 1500 { 1501 assert(resultType != NO_RESULT); // should be a real request 1502 assert(pendingRpcResultType == NO_RESULT); // only one pending RPC call at a time 1503 1504 if (tcpSocket->state() == QTcpSocket::ConnectedState) 1505 { 1506 int rpcId = nextRpcId++; 1507 call.insert("id", rpcId); 1508 1509 QByteArray request = QJsonDocument(call).toJson(QJsonDocument::Compact); 1510 1511 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: request:" << request; 1512 1513 request.append("\r\n"); 1514 1515 qint64 const n = tcpSocket->write(request); 1516 1517 if ((int) n == request.size()) 1518 { 1519 // RPC call succeeded, remember ID and expected result type 1520 pendingRpcId = rpcId; 1521 pendingRpcResultType = resultType; 1522 } 1523 else 1524 { 1525 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: unexpected short write:" << n << "bytes of" << request.size(); 1526 } 1527 } 1528 } 1529 1530 void PHD2::sendNextRpcCall() 1531 { 1532 if (pendingRpcResultType != NO_RESULT) 1533 return; // a request is currently outstanding 1534 1535 if (rpcRequestQueue.empty()) 1536 return; // no queued requests 1537 1538 RpcCall &call = rpcRequestQueue.front(); 1539 sendRpcCall(call.call, call.resultType); 1540 rpcRequestQueue.pop_front(); 1541 } 1542 1543 void PHD2::sendPHD2Request(const QString &method, const QJsonArray &args) 1544 { 1545 assert(methodResults.contains(method)); 1546 1547 PHD2ResultType resultType = methodResults[method]; 1548 1549 QJsonObject jsonRPC; 1550 1551 jsonRPC.insert("jsonrpc", "2.0"); 1552 jsonRPC.insert("method", method); 1553 1554 if (!args.empty()) 1555 jsonRPC.insert("params", args); 1556 1557 if (pendingRpcResultType == NO_RESULT) 1558 { 1559 // no outstanding rpc call, send it right off 1560 sendRpcCall(jsonRPC, resultType); 1561 } 1562 else 1563 { 1564 // there is already an outstanding call, enqueue this call 1565 // until the prior call completes 1566 1567 if (Options::verboseLogging()) 1568 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: defer call" << method; 1569 1570 rpcRequestQueue.push_back(RpcCall(jsonRPC, resultType)); 1571 } 1572 } 1573 1574 PHD2::PHD2ResultType PHD2::takeRequestFromList(const QJsonObject &response) 1575 { 1576 if (Q_UNLIKELY(!response.contains("id"))) 1577 { 1578 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with no id"; 1579 return NO_RESULT; 1580 } 1581 1582 int id = response["id"].toInt(); 1583 1584 if (Q_UNLIKELY(id != pendingRpcId)) 1585 { 1586 // RPC id mismatch -- this should never happen, something is 1587 // seriously wrong 1588 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with id" << id; 1589 return NO_RESULT; 1590 } 1591 1592 PHD2ResultType val = pendingRpcResultType; 1593 pendingRpcResultType = NO_RESULT; 1594 return val; 1595 } 1596 1597 }