Kstars

phd2.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "phd2.h"
8
9#include "Options.h"
10#include "kspaths.h"
11#include "kstars.h"
12
13#include "ekos/manager.h"
14#include "fitsviewer/fitsdata.h"
15#include "ekos/guide/guide.h"
16#include "fitsviewer/fitsview.h"
17
18#include <cassert>
19#include <fitsio.h>
20#include <KMessageBox>
21#include <QImage>
22
23#include <QJsonDocument>
24#include <QNetworkReply>
25
26#include <ekos_guide_debug.h>
27
28#define MAX_SET_CONNECTED_RETRIES 3
29
30namespace Ekos
31{
32PHD2::PHD2()
33{
34 tcpSocket = new QTcpSocket(this);
35
36 //This list of available PHD Events is on https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring
37
38 events["Version"] = Version;
39 events["LockPositionSet"] = LockPositionSet;
40 events["Calibrating"] = Calibrating;
41 events["CalibrationComplete"] = CalibrationComplete;
42 events["StarSelected"] = StarSelected;
43 events["StartGuiding"] = StartGuiding;
44 events["Paused"] = Paused;
45 events["StartCalibration"] = StartCalibration;
46 events["AppState"] = AppState;
47 events["CalibrationFailed"] = CalibrationFailed;
48 events["CalibrationDataFlipped"] = CalibrationDataFlipped;
49 events["LoopingExposures"] = LoopingExposures;
50 events["LoopingExposuresStopped"] = LoopingExposuresStopped;
51 events["SettleBegin"] = SettleBegin;
52 events["Settling"] = Settling;
53 events["SettleDone"] = SettleDone;
54 events["StarLost"] = StarLost;
55 events["GuidingStopped"] = GuidingStopped;
56 events["Resumed"] = Resumed;
57 events["GuideStep"] = GuideStep;
58 events["GuidingDithered"] = GuidingDithered;
59 events["LockPositionLost"] = LockPositionLost;
60 events["Alert"] = Alert;
61 events["GuideParamChange"] = GuideParamChange;
62 events["ConfigurationChange"] = ConfigurationChange;
63
64 //This list of available PHD Methods is on https://github.com/OpenPHDGuiding/phd2/wiki/EventMonitoring
65 //Only some of the methods are implemented. The ones that say COMMAND_RECEIVED simply return a 0 saying the command was received.
66 methodResults["capture_single_frame"] = CAPTURE_SINGLE_FRAME;
67 methodResults["clear_calibration"] = CLEAR_CALIBRATION_COMMAND_RECEIVED;
68 methodResults["dither"] = DITHER_COMMAND_RECEIVED;
69 //find_star
70 //flip_calibration
71 //get_algo_param_names
72 //get_algo_param
73 methodResults["get_app_state"] = APP_STATE_RECEIVED;
74 //get_calibrated
75 //get_calibration_data
76 methodResults["get_connected"] = IS_EQUIPMENT_CONNECTED;
77 //get_cooler_status
78 methodResults["get_current_equipment"] = GET_CURRENT_EQUIPMENT;
79 methodResults["get_dec_guide_mode"] = DEC_GUIDE_MODE;
80 methodResults["get_exposure"] = EXPOSURE_TIME;
81 methodResults["get_exposure_durations"] = EXPOSURE_DURATIONS;
82 methodResults["get_lock_position"] = LOCK_POSITION;
83 //get_lock_shift_enabled
84 //get_lock_shift_params
85 //get_paused
86 methodResults["get_pixel_scale"] = PIXEL_SCALE;
87 //get_profile
88 //get_profiles
89 //get_search_region
90 //get_sensor_temperature
91 methodResults["get_star_image"] = STAR_IMAGE;
92 //get_use_subframes
93 methodResults["guide"] = GUIDE_COMMAND_RECEIVED;
94 //guide_pulse
95 methodResults["loop"] = LOOP;
96 //save_image
97 //set_algo_param
98 methodResults["set_connected"] = CONNECTION_RESULT;
99 methodResults["set_dec_guide_mode"] = SET_DEC_GUIDE_MODE_COMMAND_RECEIVED;
100 methodResults["set_exposure"] = SET_EXPOSURE_COMMAND_RECEIVED;
101 methodResults["set_lock_position"] = SET_LOCK_POSITION;
102 //set_lock_shift_enabled
103 //set_lock_shift_params
104 methodResults["set_paused"] = SET_PAUSED_COMMAND_RECEIVED;
105 //set_profile
106 //shutdown
107 methodResults["stop_capture"] = STOP_CAPTURE_COMMAND_RECEIVED;
108
109 abortTimer = new QTimer(this);
110 connect(abortTimer, &QTimer::timeout, this, [ = ]
111 {
112 if (state == CALIBRATING)
113 qCDebug(KSTARS_EKOS_GUIDE) << "Abort timeout expired while calibrating, retrying to guide.";
114 else if (state == LOSTLOCK)
115 qCDebug(KSTARS_EKOS_GUIDE) << "Abort timeout expired while reacquiring star, retrying to guide.";
116 else
117 qCDebug(KSTARS_EKOS_GUIDE) << "Abort timeout expired, stopping.";
118 abort();
119 });
120
121 ditherTimer = new QTimer(this);
122 connect(ditherTimer, &QTimer::timeout, this, [ = ]
123 {
124 qCDebug(KSTARS_EKOS_GUIDE) << "ditherTimer expired, state" << state << "dithering" << isDitherActive << "settling" << isSettling;
125 ditherTimer->stop();
126 isDitherActive = false;
127 isSettling = false;
128 if (Options::ditherFailAbortsAutoGuide())
129 {
130 abort();
131 emit newStatus(GUIDE_DITHERING_ERROR);
132 }
133 else
134 {
135 emit newLog(i18n("PHD2: There was no dithering response from PHD2, but continue guiding."));
136 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
137 }
138 });
139
140 stateTimer = new QTimer(this);
141 connect(stateTimer, &QTimer::timeout, this, [ = ]
142 {
143 QTcpSocket::SocketState socketstate = tcpSocket->state();
144 switch (socketstate)
145 {
147 m_PHD2ReconnectCounter++;
148 if (m_PHD2ReconnectCounter > PHD2_RECONNECT_THRESHOLD)
149 {
150 stateTimer->stop();
151 emit newLog(i18n("Giving up reconnecting."));
152 }
153 else
154 {
155 emit newLog(i18n("Reconnecting to PHD2 Host: %1, on port %2. . .", Options::pHD2Host(), Options::pHD2Port()));
156
157 connect(tcpSocket, &QTcpSocket::readyRead, this, &PHD2::readPHD2, Qt::UniqueConnection);
158#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
159 connect(tcpSocket, &QTcpSocket::errorOccurred, this, &PHD2::displayError, Qt::UniqueConnection);
160#else
161 connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
162 SLOT(displayError(QAbstractSocket::SocketError)));
163#endif
164 tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port());
165 }
166 break;
168 m_PHD2ReconnectCounter = 0;
169 checkIfEquipmentConnected();
170 requestAppState();
171 break;
172 default:
173 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: TCP connection state:" << socketstate;
174 break;
175 }
176 });
177}
178
179PHD2::~PHD2()
180{
181 delete abortTimer;
182 delete ditherTimer;
183}
184
185bool PHD2::Connect()
186{
187 switch (connection)
188 {
189 case DISCONNECTED:
190 // Not yet connected, let's connect server
191 connection = CONNECTING;
192 emit newLog(i18n("Connecting to PHD2 Host: %1, on port %2. . .", Options::pHD2Host(), Options::pHD2Port()));
193
194 connect(tcpSocket, &QTcpSocket::readyRead, this, &PHD2::readPHD2, Qt::UniqueConnection);
195#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
196 connect(tcpSocket, &QTcpSocket::errorOccurred, this, &PHD2::displayError, Qt::UniqueConnection);
197#else
198 connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
199 SLOT(displayError(QAbstractSocket::SocketError)));
200#endif
201
202 tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port());
203
204 m_PHD2ReconnectCounter = 0;
205 stateTimer->start(PHD2_RECONNECT_TIMEOUT);
206 return true;
207
208 case EQUIPMENT_DISCONNECTED:
209 // Equipment disconnected from PHD2, request reconnection
210 connectEquipment(true);
211 return true;
212
213 case DISCONNECTING:
214 // Not supposed to interrupt a running disconnection
215 return false;
216
217 default:
218 return false;
219 }
220}
221
222void PHD2::ResetConnectionState()
223{
224 connection = DISCONNECTED;
225
226 // clear the outstanding and queued RPC requests
227 pendingRpcResultType = NO_RESULT;
228 rpcRequestQueue.clear();
229
230 starImageRequested = false;
231 isSettling = false;
232 isDitherActive = false;
233
234 ditherTimer->stop();
235 abortTimer->stop();
236
237 tcpSocket->disconnect(this);
238
239 emit newStatus(GUIDE_DISCONNECTED);
240}
241
242bool PHD2::Disconnect()
243{
244 switch (connection)
245 {
246 case EQUIPMENT_CONNECTED:
247 emit newLog(i18n("Aborting any capture before disconnecting equipment..."));
248 abort();
249 connection = DISCONNECTING;
250 break;
251
252 case CONNECTED:
253 case CONNECTING:
254 case EQUIPMENT_DISCONNECTED:
255 stateTimer->stop();
256 tcpSocket->disconnectFromHost();
257 ResetConnectionState();
258 if (tcpSocket->state() != QAbstractSocket::UnconnectedState)
259 tcpSocket->waitForDisconnected(5000);
260 emit newLog(i18n("Disconnected from PHD2 Host: %1, on port %2.", Options::pHD2Host(), Options::pHD2Port()));
261 break;
262
263 case DISCONNECTING:
264 case DISCONNECTED:
265 break;
266 }
267
268 return true;
269}
270
271void PHD2::displayError(QAbstractSocket::SocketError socketError)
272{
273 switch (socketError)
274 {
276 emit newLog(i18n("The host disconnected."));
277 break;
279 emit newLog(i18n("The host was not found. Please check the host name and port settings in Guide options."));
280 break;
282 emit newLog(i18n("The connection was refused by the peer. Make sure the PHD2 is running, and check that "
283 "the host name and port settings are correct."));
284 break;
285 default:
286 emit newLog(i18n("The following error occurred: %1.", tcpSocket->errorString()));
287 }
288
289 ResetConnectionState();
290
291 emit newStatus(GUIDE_DISCONNECTED);
292}
293
294void PHD2::readPHD2()
295{
296 while (!tcpSocket->atEnd() && tcpSocket->canReadLine())
297 {
298 QByteArray line = tcpSocket->readLine();
299 if (line.isEmpty())
300 continue;
301
302 QJsonParseError qjsonError;
303
304 QJsonDocument jdoc = QJsonDocument::fromJson(line, &qjsonError);
305
306 if (qjsonError.error != QJsonParseError::NoError)
307 {
308 emit newLog(i18n("PHD2: invalid response received: %1", QString(line)));
309 emit newLog(i18n("PHD2: JSON error: %1", qjsonError.errorString()));
310 continue;
311 }
312
313 QJsonObject jsonObj = jdoc.object();
314
315 if (jsonObj.contains("Event"))
316 processPHD2Event(jsonObj, line);
317 else if (jsonObj.contains("error"))
318 processPHD2Error(jsonObj, line);
319 else if (jsonObj.contains("result"))
320 processPHD2Result(jsonObj, line);
321 }
322}
323
324void PHD2::processPHD2Event(const QJsonObject &jsonEvent, const QByteArray &line)
325{
326 if (Options::verboseLogging())
327 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: event:" << line;
328
329 QString eventName = jsonEvent["Event"].toString();
330
331 if (!events.contains(eventName))
332 {
333 emit newLog(i18n("Unknown PHD2 event: %1", eventName));
334 return;
335 }
336
337 event = events.value(eventName);
338
339 switch (event)
340 {
341 case Version:
342 emit newLog(i18n("PHD2: Version %1", jsonEvent["PHDVersion"].toString()));
343 break;
344
345 case CalibrationComplete:
346 emit newLog(i18n("PHD2: Calibration Complete."));
347 emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
348 break;
349
350 case StartGuiding:
351 updateGuideParameters();
352 requestCurrentEquipmentUpdate();
353 // Do not report guiding as started because it will start scheduled capture before guiding is settled
354 // just print the log message and GUIDE_STARTED status will be set in SettleDone
355 // phd2 will always send SettleDone event
356 emit newLog(i18n("PHD2: Waiting for guiding to settle."));
357 break;
358
359 case Paused:
360 handlePHD2AppState(PAUSED);
361 break;
362
363 case StartCalibration:
364 handlePHD2AppState(CALIBRATING);
365 break;
366
367 case AppState:
368 // AppState is the last of the initial messages received when we first connect to PHD2
369 processPHD2State(jsonEvent["State"].toString());
370 // if the equipment is not already connected, then try to connect it.
371 if (connection == CONNECTING)
372 {
373 emit newLog("PHD2: Connecting equipment and external guider...");
374 connectEquipment(true);
375 }
376 break;
377
378 case CalibrationFailed:
379 emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString()));
380 handlePHD2AppState(STOPPED);
381 break;
382
383 case CalibrationDataFlipped:
384 emit newLog(i18n("Calibration Data Flipped."));
385 break;
386
387 case LoopingExposures:
388 handlePHD2AppState(LOOPING);
389 break;
390
391 case LoopingExposuresStopped:
392 handlePHD2AppState(STOPPED);
393 break;
394
395 case Calibrating:
396 case Settling:
397 case SettleBegin:
398 //This can happen for guiding or for dithering. A Settle done event will arrive when it finishes.
399 break;
400
401 case SettleDone:
402 {
403 // guiding stopped during dithering
404 if (state == PHD2::STOPPED)
405 return;
406
407 bool error = false;
408
409 if (jsonEvent["Status"].toInt() != 0)
410 {
411 error = true;
412 emit newLog(i18n("PHD2: Settling failed (%1).", jsonEvent["Error"].toString()));
413 }
414
415 bool wasDithering = isDitherActive;
416
417 isDitherActive = false;
418 isSettling = false;
419
420 if (wasDithering)
421 {
422 ditherTimer->stop();
423 if (error && Options::ditherFailAbortsAutoGuide())
424 {
425 abort();
426 emit newStatus(GUIDE_DITHERING_ERROR);
427 }
428 else
429 {
430 if (error)
431 emit newLog(i18n("PHD2: There was a dithering error, but continue guiding."));
432
433 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
434 }
435 }
436 else
437 {
438 if (error)
439 {
440 emit newLog(i18n("PHD2: Settling failed, aborted."));
441 emit newStatus(GUIDE_ABORTED);
442 }
443 else
444 {
445 // settle completed after "guide" command
446 emit newLog(i18n("PHD2: Settling complete, Guiding Started."));
447 emit newStatus(GUIDE_GUIDING);
448 }
449 }
450 }
451 break;
452
453 case StarSelected:
454 handlePHD2AppState(SELECTED);
455 break;
456
457 case StarLost:
458 // If we lost the guide star, let the state and abort timers update our state
459 handlePHD2AppState(LOSTLOCK);
460 break;
461
462 case GuidingStopped:
463 handlePHD2AppState(STOPPED);
464 break;
465
466 case Resumed:
467 handlePHD2AppState(GUIDING);
468 break;
469
470 case GuideStep:
471 {
472 // If we lost the guide star, let the state timer update our state
473 // Sometimes PHD2 is actually not guiding at that time, so we'll either resume or abort
474 if (state == LOSTLOCK)
475 emit newLog(i18n("PHD2: Star found, guiding is resuming..."));
476
477 if (isDitherActive)
478 return;
479
480 double diff_ra_pixels, diff_de_pixels, diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec, snr;
481 QString RADirection, DECDirection;
482 diff_ra_pixels = jsonEvent["RADistanceRaw"].toDouble();
483 diff_de_pixels = jsonEvent["DECDistanceRaw"].toDouble();
484 pulse_ra = jsonEvent["RADuration"].toDouble();
485 pulse_dec = jsonEvent["DECDuration"].toDouble();
486 RADirection = jsonEvent["RADirection"].toString();
487 DECDirection = jsonEvent["DECDirection"].toString();
488 snr = jsonEvent["SNR"].toDouble();
489
490 if (RADirection == "East")
491 pulse_ra = -pulse_ra; //West Direction is Positive, East is Negative
492 if (DECDirection == "South")
493 pulse_dec = -pulse_dec; //South Direction is Negative, North is Positive
494
495 //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.
496 if (pixelScale != 0)
497 {
498 diff_ra_arcsecs = diff_ra_pixels * pixelScale;
499 diff_de_arcsecs = diff_de_pixels * pixelScale;
500 }
501 else
502 {
503 diff_ra_arcsecs = 206.26480624709 * diff_ra_pixels * ccdPixelSizeX / mountFocalLength;
504 diff_de_arcsecs = 206.26480624709 * diff_de_pixels * ccdPixelSizeY / mountFocalLength;
505 }
506
507 if (std::isfinite(snr))
508 emit newSNR(snr);
509
510 if (std::isfinite(diff_ra_arcsecs) && std::isfinite(diff_de_arcsecs))
511 {
512 errorLog.append(QPointF(diff_ra_arcsecs, diff_de_arcsecs));
513 if(errorLog.size() > 50)
514 errorLog.remove(0);
515
516 emit newAxisDelta(diff_ra_arcsecs, diff_de_arcsecs);
517 emit newAxisPulse(pulse_ra, pulse_dec);
518
519 // Does PHD2 real a sky background or num-stars measure?
520 emit guideStats(diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec,
521 std::isfinite(snr) ? snr : 0, 0, 0);
522
523 double total_sqr_RA_error = 0.0;
524 double total_sqr_DE_error = 0.0;
525
526 for (auto &point : errorLog)
527 {
528 total_sqr_RA_error += point.x() * point.x();
529 total_sqr_DE_error += point.y() * point.y();
530 }
531
532 emit newAxisSigma(sqrt(total_sqr_RA_error / errorLog.size()), sqrt(total_sqr_DE_error / errorLog.size()));
533
534 }
535 //Note that if it is receiving full size remote images, it should not get the guide star image.
536 //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
537 //If we are getting the full size image, we will want to know the lock position for the image that loads in the viewer.
538 if ( Options::guideSubframe() || currentCameraIsNotInEkos )
539 requestStarImage(32); //This requests a star image for the guide view. 32 x 32 pixels
540 else
541 requestLockPosition();
542 }
543 break;
544
545 case GuidingDithered:
546 break;
547
548 case LockPositionSet:
549 handlePHD2AppState(SELECTED);
550 break;
551
552 case LockPositionLost:
553 handlePHD2AppState(LOSTLOCK);
554 break;
555
556 case Alert:
557 emit newLog(i18n("PHD2 %1: %2", jsonEvent["Type"].toString(), jsonEvent["Msg"].toString()));
558 break;
559
560 case GuideParamChange:
561 case ConfigurationChange:
562 //Don't do anything for now, might change this later.
563 //Some Possible Parameter Names:
564 //Backlash comp enabled, Backlash comp amount,
565 //For Each Axis: MinMove, Max Duration,
566 //PPEC aggressiveness, PPEC prediction weight,
567 //Resist switch minimum motion, Resist switch aggression,
568 //Low-pass minimum move, Low-pass slope weight,
569 //Low-pass2 minimum move, Low-pass2 aggressiveness,
570 //Hysteresis hysteresis, Hysteresis aggression
571 break;
572
573 }
574}
575
576void PHD2::processPHD2State(const QString &phd2State)
577{
578 if (phd2State == "Stopped")
579 handlePHD2AppState(STOPPED);
580 else if (phd2State == "Selected")
581 handlePHD2AppState(SELECTED);
582 else if (phd2State == "Calibrating")
583 handlePHD2AppState(CALIBRATING);
584 else if (phd2State == "Guiding")
585 handlePHD2AppState(GUIDING);
586 else if (phd2State == "LostLock")
587 handlePHD2AppState(LOSTLOCK);
588 else if (phd2State == "Paused")
589 handlePHD2AppState(PAUSED);
590 else if (phd2State == "Looping")
591 handlePHD2AppState(LOOPING);
592 else emit newLog(QString("PHD2: Unsupported app state ") + phd2State + ".");
593}
594
595void PHD2::handlePHD2AppState(PHD2State newstate)
596{
597 // do not handle the same state twice
598 if (state == newstate)
599 return;
600
601 switch (newstate)
602 {
603 case STOPPED:
604 switch (state)
605 {
606 case CALIBRATING:
607 //emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString()));
608 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
609 break;
610 case LOOPING:
611 emit newLog(i18n("PHD2: Looping Exposures Stopped."));
612 emit newStatus(Ekos::GUIDE_IDLE);
613 break;
614 case GUIDING:
615 case LOSTLOCK:
616 emit newLog(i18n("PHD2: Guiding Stopped."));
617 emit newStatus(Ekos::GUIDE_ABORTED);
618 break;
619 default:
620 if (connection == DISCONNECTING)
621 {
622 emit newLog("PHD2: Disconnecting equipment and external guider...");
623 connectEquipment(false);
624 }
625 break;
626 }
627 break;
628
629 case SELECTED:
630 switch (state)
631 {
632 case STOPPED:
633 case CALIBRATING:
634 case GUIDING:
635 emit newLog(i18n("PHD2: Lock Position Set."));
636 if (isSettling)
637 {
638 newstate = CALIBRATING;
639 emit newStatus(Ekos::GUIDE_CALIBRATING);
640 }
641 break;
642 case DITHERING:
643 // do nothing, this is the initial step in PHD2 for dithering
644 break;
645 default:
646 emit newLog(i18n("PHD2: Star Selected."));
647 emit newStatus(GUIDE_STAR_SELECT);
648 }
649 break;
650
651 case GUIDING:
652 switch (state)
653 {
654 case PAUSED:
655 case DITHERING:
656 emit newLog(i18n("PHD2: Dithering successful."));
657 abortTimer->stop();
658 ditherTimer->stop(); // stop immediately, since pausing could interrupt settling
659 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
660 break;
661 default:
662 emit newLog(i18n("PHD2: Guiding started."));
663 abortTimer->stop();
664 emit newStatus(Ekos::GUIDE_GUIDING);
665 break;
666 }
667 break;
668
669 case LOSTLOCK:
670 switch (state)
671 {
672 case CALIBRATING:
673 emit newLog(i18n("PHD2: Lock Position Lost, continuing calibration."));
674 // Don't be paranoid, accept star-lost events during calibration and trust PHD2 to complete
675 //newstate = STOPPED;
676 //emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
677 break;
678 case GUIDING:
679 emit newLog(i18n("PHD2: Star Lost. Trying to reacquire for %1s.", Options::guideLostStarTimeout()));
680 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000);
681 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout started (" << Options::guideLostStarTimeout() << " sec)";
682 emit newStatus(Ekos::GUIDE_REACQUIRE);
683 break;
684 default:
685 emit newLog(i18n("PHD2: Lock Position Lost."));
686 break;
687 }
688 break;
689
690 case PAUSED:
691 emit newLog(i18n("PHD2: Guiding paused."));
692 emit newStatus(GUIDE_SUSPENDED);
693 break;
694
695 case CALIBRATING:
696 emit newLog(i18n("PHD2: Calibrating, timing out in %1s.", Options::guideCalibrationTimeout()));
697 abortTimer->start(static_cast<int>(Options::guideCalibrationTimeout()) * 1000);
698 emit newStatus(GUIDE_CALIBRATING);
699 break;
700
701 case LOOPING:
702 switch (state)
703 {
704 case CALIBRATING:
705 emit newLog(i18n("PHD2: Calibration turned to looping, failed."));
706 emit newStatus(GUIDE_CALIBRATION_ERROR);
707 break;
708 default:
709 emit newLog(i18n("PHD2: Looping Exposures."));
710 emit newStatus(GUIDE_LOOPING);
711 break;
712 }
713 break;
714 case DITHERING:
715 emit newLog(i18n("PHD2: Dithering started."));
716 emit newStatus(GUIDE_DITHERING);
717 break;
718 }
719
720 state = newstate;
721}
722
723void PHD2::processPHD2Result(const QJsonObject &jsonObj, const QByteArray &line)
724{
725 PHD2ResultType resultType = takeRequestFromList(jsonObj);
726
727 if (resultType == STAR_IMAGE)
728 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received star image response, id" <<
729 jsonObj["id"].toInt(); // don't spam the log with image data
730 else
731 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: response:" << line;
732
733 switch (resultType)
734 {
735 case NO_RESULT:
736 //Ekos didn't ask for this result?
737 break;
738
739 case CAPTURE_SINGLE_FRAME: //capture_single_frame
740 break;
741
742 case CLEAR_CALIBRATION_COMMAND_RECEIVED: //clear_calibration
743 emit newLog(i18n("PHD2: Calibration is cleared"));
744 break;
745
746 case DITHER_COMMAND_RECEIVED: //dither
747 handlePHD2AppState(DITHERING);
748 break;
749
750 //find_star
751 //flip_calibration
752 //get_algo_param_names
753 //get_algo_param
754
755 case APP_STATE_RECEIVED: //get_app_state
756 {
757 QString state = jsonObj["State"].toString();
758 if (state.isEmpty())
759 state = jsonObj["result"].toString();
760 if (state.isEmpty())
761 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received unsupported app state";
762 else
763 processPHD2State(state);
764 }
765 break;
766
767 //get_calibrated
768 //get_calibration_data
769
770 case IS_EQUIPMENT_CONNECTED: //get_connected
771 {
772 bool isConnected = jsonObj["result"].toBool();
773 switch (connection)
774 {
775 case CONNECTING:
776 // We just plugged in server, request equipment connection if needed
777 if (isConnected)
778 {
779 connection = CONNECTED;
780 setEquipmentConnected();
781 }
782 else connectEquipment(true);
783 break;
784
785 case CONNECTED:
786 // We were waiting for equipment to be connected after plugging in server
787 if (isConnected)
788 setEquipmentConnected();
789 break;
790
791 case DISCONNECTING:
792 // We were waiting for equipment to be disconnected before unplugging from server
793 if (!isConnected)
794 {
795 connection = EQUIPMENT_DISCONNECTED;
796 Disconnect();
797 }
798 else connectEquipment(false);
799 break;
800
801 case EQUIPMENT_CONNECTED:
802 // Equipment was disconnected from PHD2 side, so notify clients and wait.
803 if (!isConnected)
804 {
805 // TODO: setEquipmentDisconnected()
806 connection = EQUIPMENT_DISCONNECTED;
807 emit newStatus(Ekos::GUIDE_DISCONNECTED);
808 }
809 break;
810
811 case DISCONNECTED:
812 case EQUIPMENT_DISCONNECTED:
813 // Equipment was connected from PHD2 side, so notify clients and wait.
814 if (isConnected)
815 setEquipmentConnected();
816 break;
817 }
818 }
819 break;
820
821 //get_cooler_status
822 case GET_CURRENT_EQUIPMENT: //get_current_equipment
823 {
824 QJsonObject equipObject = jsonObj["result"].toObject();
825 currentCamera = equipObject["camera"].toObject()["name"].toString();
826 currentMount = equipObject["mount"].toObject()["name"].toString();
827 currentAuxMount = equipObject["aux_mount"].toObject()["name"].toString();
828
829 emit guideEquipmentUpdated();
830
831 break;
832 }
833
834
835 case DEC_GUIDE_MODE: //get_dec_guide_mode
836 {
837 QString mode = jsonObj["result"].toString();
838 Ekos::Manager::Instance()->guideModule()->updateDirectionsFromPHD2(mode);
839 emit newLog(i18n("PHD2: DEC Guide Mode is Set to: %1", mode));
840 }
841 break;
842
843
844 case EXPOSURE_TIME: //get_exposure
845 {
846 int exposurems = jsonObj["result"].toInt();
847 double exposureTime = exposurems / 1000.0;
848 Ekos::Manager::Instance()->guideModule()->setExposure(exposureTime);
849 emit newLog(i18n("PHD2: Exposure Time set to: ") + QString::number(exposureTime, 'f', 2));
850 break;
851 }
852
853
854 case EXPOSURE_DURATIONS: //get_exposure_durations
855 {
856 QVariantList exposureListArray = jsonObj["result"].toArray().toVariantList();
857 logValidExposureTimes = i18n("PHD2: Valid Exposure Times: Auto, ");
858 QList<double> values;
859 for(int i = 1; i < exposureListArray.size();
860 i ++) //For some reason PHD2 has a negative exposure time of 1 at the start of the array?
861 values << exposureListArray.at(i).toDouble() / 1000.0; //PHD2 reports in ms.
862 logValidExposureTimes += Ekos::Manager::Instance()->guideModule()->setRecommendedExposureValues(values);
863 emit newLog(logValidExposureTimes);
864 break;
865 }
866 case LOCK_POSITION: //get_lock_position
867 {
868 if(jsonObj["result"].toArray().count() == 2)
869 {
870 double x = jsonObj["result"].toArray().at(0).toDouble();
871 double y = jsonObj["result"].toArray().at(1).toDouble();
872 QVector3D newStarCenter(x, y, 0);
873 emit newStarPosition(newStarCenter, true);
874
875 //This is needed so that PHD2 sends the new star pixmap when
876 //remote images are enabled.
877 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap());
878 }
879 break;
880 }
881 //get_lock_shift_enabled
882 //get_lock_shift_params
883 //get_paused
884
885 case PIXEL_SCALE: //get_pixel_scale
886 pixelScale = jsonObj["result"].toDouble();
887 if (pixelScale == 0)
888 emit newLog(i18n("PHD2: Please set CCD and telescope parameters in PHD2, Pixel Scale is invalid."));
889 else
890 emit newLog(i18n("PHD2: Pixel Scale is %1 arcsec per pixel", QString::number(pixelScale, 'f', 2)));
891 break;
892
893 //get_profile
894 //get_profiles
895 //get_search_region
896 //get_sensor_temperature
897
898 case STAR_IMAGE: //get_star_image
899 {
900 starImageRequested = false;
901 QJsonObject jsonResult = jsonObj["result"].toObject();
902 processStarImage(jsonResult);
903 break;
904 }
905
906 //get_use_subframes
907
908 case GUIDE_COMMAND_RECEIVED: //guide
909 if (0 != jsonObj["result"].toInt(0))
910 {
911 emit newLog("PHD2: Guide command was rejected.");
912 handlePHD2AppState(STOPPED);
913 }
914 break;
915
916 //guide_pulse
917
918 case LOOP: //loop
919 handlePHD2AppState(jsonObj["result"].toBool() ? LOOPING : STOPPED);
920 break;
921
922 //save_image
923 //set_algo_param
924
925 case CONNECTION_RESULT: //set_connected
926 checkIfEquipmentConnected();
927 break;
928
929 case SET_DEC_GUIDE_MODE_COMMAND_RECEIVED: //set_dec_guide_mode
930 checkDEGuideMode();
931 break;
932
933 case SET_EXPOSURE_COMMAND_RECEIVED: //set_exposure
934 requestExposureTime(); //This will check what it was set to and print a message as to what it is.
935 break;
936
937 case SET_LOCK_POSITION: //set_lock_position
938 handlePHD2AppState(SELECTED);
939 break;
940
941 //set_lock_shift_enabled
942 //set_lock_shift_params
943
944 case SET_PAUSED_COMMAND_RECEIVED: //set_paused
945 handlePHD2AppState(PAUSED);
946 break;
947 //set_profile
948 //shutdown
949
950 case STOP_CAPTURE_COMMAND_RECEIVED: //stop_capture
951 handlePHD2AppState(STOPPED);
952 //emit newStatus(GUIDE_ABORTED);
953 break;
954 }
955
956 // send the next pending call
957 sendNextRpcCall();
958}
959
960void PHD2::processPHD2Error(const QJsonObject &jsonError, const QByteArray &line)
961{
962 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: error:" << line;
963
964 QJsonObject jsonErrorObject = jsonError["error"].toObject();
965
966 PHD2ResultType resultType = takeRequestFromList(jsonError);
967
968 // This means the user mistakenly entered an invalid exposure time.
969 switch (resultType)
970 {
971 case SET_EXPOSURE_COMMAND_RECEIVED:
972 emit newLog(logValidExposureTimes); //This will let the user know the valid exposure durations
973 QTimer::singleShot(300, [ = ] {requestExposureTime();}); //This will reset the Exposure time in Ekos to PHD2's current exposure time after a third of a second.
974 break;
975
976 case CONNECTION_RESULT:
977 connection = EQUIPMENT_DISCONNECTED;
978 emit newStatus(Ekos::GUIDE_DISCONNECTED);
979 break;
980
981 case DITHER_COMMAND_RECEIVED:
982 ditherTimer->stop();
983 isSettling = false;
984 isDitherActive = false;
985 emit newStatus(GUIDE_DITHERING_ERROR);
986
987 if (Options::ditherFailAbortsAutoGuide())
988 {
989 abort();
990 emit newLog("PHD2: failing after dithering aborts.");
991 emit newStatus(GUIDE_ABORTED);
992 }
993 else
994 {
995 // !FIXME-ag why is this trying to resume (un-pause)?
996 resume();
997 }
998 break;
999
1000 case GUIDE_COMMAND_RECEIVED:
1001 isSettling = false;
1002 break;
1003
1004 default:
1005 emit newLog(i18n("PHD2 Error: unhandled '%1'", jsonErrorObject["message"].toString()));
1006 break;
1007 }
1008
1009 // send the next pending call
1010 sendNextRpcCall();
1011}
1012
1013//These methods process the Star Images the PHD2 provides
1014
1015void PHD2::setGuideView(const QSharedPointer<FITSView> &guideView)
1016{
1017 m_GuideFrame = guideView;
1018}
1019
1020void PHD2::processStarImage(const QJsonObject &jsonStarFrame)
1021{
1022 //The width and height of the received PHD2 Star Image
1023 int width = jsonStarFrame["width"].toInt();
1024 int height = jsonStarFrame["height"].toInt();
1025
1026 //This section sets up the FITS File
1027 fitsfile *fptr = nullptr;
1028 int status = 0;
1029 long fpixel = 1, naxis = 2, nelements, exposure;
1030 long naxes[2] = { width, height };
1031 char error_status[512] = {0};
1032
1033 void* fits_buffer = nullptr;
1034 size_t fits_buffer_size = 0;
1035 if (fits_create_memfile(&fptr, &fits_buffer, &fits_buffer_size, 4096, realloc, &status))
1036 {
1037 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_file failed:" << error_status;
1038 return;
1039 }
1040
1041 if (fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status))
1042 {
1043 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_img failed:" << error_status;
1044 status = 0;
1045 fits_close_file(fptr, &status);
1046 free(fits_buffer);
1047 return;
1048 }
1049
1050 //Note, this is made up. If you want the actual exposure time, you have to request it from PHD2
1051 exposure = 1;
1052 fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status);
1053
1054 //This section takes the Pixels from the JSON Document
1055 //Then it converts from base64 to a QByteArray
1056 //Then it creates a datastream from the QByteArray to the pixel array for the FITS File
1057 QByteArray converted = QByteArray::fromBase64(jsonStarFrame["pixels"].toString().toLocal8Bit());
1058
1059 //This finishes up and closes the FITS file
1060 nelements = naxes[0] * naxes[1];
1061 if (fits_write_img(fptr, TUSHORT, fpixel, nelements, converted.data(), &status))
1062 {
1063 fits_get_errstatus(status, error_status);
1064 qCWarning(KSTARS_EKOS_GUIDE) << "fits_write_img failed:" << error_status;
1065 status = 0;
1066 fits_close_file(fptr, &status);
1067 free(fits_buffer);
1068 return;
1069 }
1070
1071 if (fits_flush_file(fptr, &status))
1072 {
1073 fits_get_errstatus(status, error_status);
1074 qCWarning(KSTARS_EKOS_GUIDE) << "fits_flush_file failed:" << error_status;
1075 status = 0;
1076 fits_close_file(fptr, &status);
1077 free(fits_buffer);
1078 return;
1079 }
1080
1081 if (fits_close_file(fptr, &status))
1082 {
1083 fits_get_errstatus(status, error_status);
1084 qCWarning(KSTARS_EKOS_GUIDE) << "fits_close_file failed:" << error_status;
1085 free(fits_buffer);
1086 return;
1087 }
1088
1089 //This loads the FITS file in the Guide FITSView
1090 //Then it updates the Summary Screen
1092 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(fits_buffer), fits_buffer_size);
1093 fdata.reset(new FITSData(), &QObject::deleteLater);
1094 fdata->setExtension(QString("fits"));
1095 fdata->loadFromBuffer(buffer);
1096 free(fits_buffer);
1097 m_GuideFrame->loadData(fdata);
1098
1099 m_GuideFrame->updateFrame();
1100 m_GuideFrame->setTrackingBox(QRect(0, 0, width, height));
1101 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap());
1102}
1103
1104void PHD2::setEquipmentConnected()
1105{
1106 if (connection != EQUIPMENT_CONNECTED)
1107 {
1108 setConnectedRetries = 0;
1109 connection = EQUIPMENT_CONNECTED;
1110 emit newStatus(Ekos::GUIDE_CONNECTED);
1111 updateGuideParameters();
1112 requestExposureDurations();
1113 requestCurrentEquipmentUpdate();
1114 }
1115}
1116
1117void PHD2::updateGuideParameters()
1118{
1119 if (pixelScale == 0)
1120 requestPixelScale();
1121 requestExposureTime();
1122 checkDEGuideMode();
1123}
1124
1125//This section handles the methods/requests sent to PHD2, some are not implemented.
1126
1127//capture_single_frame
1128void PHD2::captureSingleFrame()
1129{
1130 sendPHD2Request("capture_single_frame");
1131}
1132
1133//clear_calibration
1134bool PHD2::clearCalibration()
1135{
1136 if (connection != EQUIPMENT_CONNECTED)
1137 {
1138 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1139 emit newStatus(Ekos::GUIDE_ABORTED);
1140 return false;
1141 }
1142
1143 QJsonArray args;
1144 //This instructs PHD2 which calibration to clear.
1145 args << "mount";
1146 sendPHD2Request("clear_calibration", args);
1147
1148 return true;
1149}
1150
1151//dither
1152bool PHD2::dither(double pixels)
1153{
1154 if (connection != EQUIPMENT_CONNECTED)
1155 {
1156 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1157 emit newStatus(Ekos::GUIDE_ABORTED);
1158 return false;
1159 }
1160
1161 if (isSettling)
1162 {
1163 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring dither requested while already settling";
1164
1165 if (!isDitherActive)
1166 {
1167 // act like we just dithered so we get the appropriate
1168 // effects after the settling completes
1169 handlePHD2AppState(DITHERING);
1170 isDitherActive = true;
1171 }
1172 return true;
1173 }
1174
1175 QJsonArray args;
1176 QJsonObject settle;
1177
1178 int ditherTimeout = static_cast<int>(Options::ditherTimeout());
1179
1180 settle.insert("pixels", static_cast<double>(Options::ditherThreshold()));
1181 settle.insert("time", static_cast<int>(Options::ditherSettle()));
1182 settle.insert("timeout", ditherTimeout);
1183
1184 // Pixels
1185 args << pixels;
1186 // RA Only?
1187 args << false;
1188 // Settle
1189 args << settle;
1190
1191 isSettling = true;
1192 isDitherActive = true;
1193
1194 // PHD2 will send a SettleDone event shortly after the settling
1195 // timeout in PHD2. We don't really need a timer here, but we'll
1196 // set one anyway (belt and suspenders). Make sure to give an
1197 // extra time allowance since PHD2 won't report its timeout until
1198 // the completion of the next guide exposure after the timeout
1199 // period expires.
1200 enum { TIMEOUT_EXTRA_SECONDS = 60 }; // at least as long as any reasonable guide exposure
1201 int millis = (ditherTimeout + TIMEOUT_EXTRA_SECONDS) * 1000;
1202 ditherTimer->start(millis);
1203
1204 sendPHD2Request("dither", args);
1205
1206 handlePHD2AppState(DITHERING);
1207
1208 return true;
1209}
1210
1211//find_star
1212//flip_calibration
1213//get_algo_param_names
1214//get_algo_param
1215
1216//get_app_state
1217void PHD2::requestAppState()
1218{
1219 sendPHD2Request("get_app_state");
1220}
1221
1222//get_calibrated
1223//get_calibration_data
1224
1225//get_connected
1226void PHD2::checkIfEquipmentConnected()
1227{
1228 sendPHD2Request("get_connected");
1229}
1230
1231//get_cooler_status
1232//get_current_equipment
1233void PHD2::requestCurrentEquipmentUpdate()
1234{
1235 sendPHD2Request("get_current_equipment");
1236}
1237
1238//get_dec_guide_mode
1239void PHD2::checkDEGuideMode()
1240{
1241 sendPHD2Request("get_dec_guide_mode");
1242}
1243
1244//get_exposure
1245void PHD2::requestExposureTime()
1246{
1247 sendPHD2Request("get_exposure");
1248}
1249
1250//get_exposure_durations
1251void PHD2::requestExposureDurations()
1252{
1253 sendPHD2Request("get_exposure_durations");
1254}
1255
1256//get_lock_position
1257void PHD2::requestLockPosition()
1258{
1259 sendPHD2Request("get_lock_position");
1260}
1261//get_lock_shift_enabled
1262//get_lock_shift_params
1263//get_paused
1264
1265//get_pixel_scale
1266void PHD2::requestPixelScale()
1267{
1268 sendPHD2Request("get_pixel_scale");
1269}
1270
1271//get_profile
1272//get_profiles
1273//get_search_region
1274//get_sensor_temperature
1275
1276//get_star_image
1277void PHD2::requestStarImage(int size)
1278{
1279 if (starImageRequested)
1280 {
1281 if (Options::verboseLogging())
1282 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: skip extra star image request";
1283 return;
1284 }
1285
1286 QJsonArray args2;
1287 args2 << size; // This is both the width and height.
1288 sendPHD2Request("get_star_image", args2);
1289
1290 starImageRequested = true;
1291}
1292
1293//get_use_subframes
1294
1295//guide
1296bool PHD2::guide()
1297{
1298 if (state == GUIDING)
1299 {
1300 emit newLog(i18n("PHD2: Guiding is already running."));
1301 emit newStatus(Ekos::GUIDE_GUIDING);
1302 return true;
1303 }
1304
1305 if (connection != EQUIPMENT_CONNECTED)
1306 {
1307 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1308 emit newStatus(Ekos::GUIDE_ABORTED);
1309 return false;
1310 }
1311
1312 QJsonArray args;
1313 QJsonObject settle;
1314
1315 settle.insert("pixels", static_cast<double>(Options::ditherThreshold()));
1316 settle.insert("time", static_cast<int>(Options::ditherSettle()));
1317 settle.insert("timeout", static_cast<int>(Options::ditherTimeout()));
1318
1319 // Settle param
1320 args << settle;
1321 // Recalibrate param
1322 args << false;
1323
1324 errorLog.clear();
1325
1326 isSettling = true;
1327 sendPHD2Request("guide", args);
1328
1329 return true;
1330}
1331
1332//guide_pulse
1333//loop
1334void PHD2::loop()
1335{
1336 sendPHD2Request("loop");
1337}
1338//save_image
1339//set_algo_param
1340
1341//set_connected
1342void PHD2::connectEquipment(bool enable)
1343{
1344 if (connection == EQUIPMENT_CONNECTED && enable == true)
1345 return;
1346
1347 if (connection == EQUIPMENT_DISCONNECTED && enable == false)
1348 return;
1349
1350 if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES)
1351 {
1352 setConnectedRetries = 0;
1353 connection = EQUIPMENT_DISCONNECTED;
1354 emit newStatus(Ekos::GUIDE_DISCONNECTED);
1355 return;
1356 }
1357
1358 pixelScale = 0 ;
1359
1360 QJsonArray args;
1361
1362 // connected = enable
1363 args << enable;
1364
1365 if (enable)
1366 emit newLog(i18n("PHD2: Connecting Equipment. . ."));
1367 else
1368 emit newLog(i18n("PHD2: Disconnecting Equipment. . ."));
1369
1370 sendPHD2Request("set_connected", args);
1371}
1372
1373//set_dec_guide_mode
1374void PHD2::requestSetDEGuideMode(bool deEnabled, bool nEnabled,
1375 bool sEnabled) //Possible Settings Off, Auto, North, and South
1376{
1377 QJsonArray args;
1378
1379 if(deEnabled)
1380 {
1381 if(nEnabled && sEnabled)
1382 args << "Auto";
1383 else if(nEnabled)
1384 args << "North";
1385 else if(sEnabled)
1386 args << "South";
1387 else
1388 args << "Off";
1389 }
1390 else
1391 {
1392 args << "Off";
1393 }
1394
1395 sendPHD2Request("set_dec_guide_mode", args);
1396}
1397
1398//set_exposure
1399void PHD2::requestSetExposureTime(int time) //Note: time is in milliseconds
1400{
1401 QJsonArray args;
1402 args << time;
1403 sendPHD2Request("set_exposure", args);
1404}
1405
1406//set_lock_position
1407void PHD2::setLockPosition(double x, double y)
1408{
1409 // Note: false will mean if a guide star is near the coordinates, it will use that.
1410 QJsonArray args;
1411 args << x << y << false;
1412 sendPHD2Request("set_lock_position", args);
1413}
1414//set_lock_shift_enabled
1415//set_lock_shift_params
1416
1417//set_paused
1418bool PHD2::suspend()
1419{
1420 if (connection != EQUIPMENT_CONNECTED)
1421 {
1422 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1423 emit newStatus(Ekos::GUIDE_ABORTED);
1424 return false;
1425 }
1426
1427 QJsonArray args;
1428
1429 // Paused param
1430 args << true;
1431 // FULL param
1432 args << "full";
1433
1434 sendPHD2Request("set_paused", args);
1435
1436 if (abortTimer->isActive())
1437 {
1438 // Avoid that the a preceding lost star event leads to an abort while guiding is suspended.
1439 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout cancelled.";
1440 abortTimer->stop();
1441 }
1442
1443 return true;
1444}
1445
1446//set_paused (also)
1447bool PHD2::resume()
1448{
1449 if (connection != EQUIPMENT_CONNECTED)
1450 {
1451 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1452 emit newStatus(Ekos::GUIDE_ABORTED);
1453 return false;
1454 }
1455
1456 QJsonArray args;
1457
1458 // Paused param
1459 args << false;
1460
1461 sendPHD2Request("set_paused", args);
1462
1463 if (state == LOSTLOCK)
1464 {
1465 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout restarted.";
1466 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000);
1467 }
1468
1469 return true;
1470}
1471
1472//set_profile
1473//shutdown
1474
1475//stop_capture
1476bool PHD2::abort()
1477{
1478 if (connection != EQUIPMENT_CONNECTED)
1479 {
1480 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1481 emit newStatus(Ekos::GUIDE_ABORTED);
1482 return false;
1483 }
1484
1485 abortTimer->stop();
1486
1487 sendPHD2Request("stop_capture");
1488 return true;
1489}
1490
1491//This method is not handled by PHD2
1492bool PHD2::calibrate()
1493{
1494 // We don't explicitly do calibration since it is done in the guide step by PHD2 anyway
1495 //emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
1496 return true;
1497}
1498
1499//This is how information requests and commands for PHD2 are handled
1500
1501void PHD2::sendRpcCall(QJsonObject &call, PHD2ResultType resultType)
1502{
1503 assert(resultType != NO_RESULT); // should be a real request
1504 assert(pendingRpcResultType == NO_RESULT); // only one pending RPC call at a time
1505
1506 if (tcpSocket->state() == QTcpSocket::ConnectedState)
1507 {
1508 int rpcId = nextRpcId++;
1509 call.insert("id", rpcId);
1510
1512
1513 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: request:" << request;
1514
1515 request.append("\r\n");
1516
1517 qint64 const n = tcpSocket->write(request);
1518
1519 if ((int) n == request.size())
1520 {
1521 // RPC call succeeded, remember ID and expected result type
1522 pendingRpcId = rpcId;
1523 pendingRpcResultType = resultType;
1524 }
1525 else
1526 {
1527 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: unexpected short write:" << n << "bytes of" << request.size();
1528 }
1529 }
1530}
1531
1532void PHD2::sendNextRpcCall()
1533{
1534 if (pendingRpcResultType != NO_RESULT)
1535 return; // a request is currently outstanding
1536
1537 if (rpcRequestQueue.empty())
1538 return; // no queued requests
1539
1540 RpcCall &call = rpcRequestQueue.front();
1541 sendRpcCall(call.call, call.resultType);
1542 rpcRequestQueue.pop_front();
1543}
1544
1545void PHD2::sendPHD2Request(const QString &method, const QJsonArray &args)
1546{
1547 assert(methodResults.contains(method));
1548
1549 PHD2ResultType resultType = methodResults[method];
1550
1551 QJsonObject jsonRPC;
1552
1553 jsonRPC.insert("jsonrpc", "2.0");
1554 jsonRPC.insert("method", method);
1555
1556 if (!args.empty())
1557 jsonRPC.insert("params", args);
1558
1559 if (pendingRpcResultType == NO_RESULT)
1560 {
1561 // no outstanding rpc call, send it right off
1562 sendRpcCall(jsonRPC, resultType);
1563 }
1564 else
1565 {
1566 // there is already an outstanding call, enqueue this call
1567 // until the prior call completes
1568
1569 if (Options::verboseLogging())
1570 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: defer call" << method;
1571
1572 rpcRequestQueue.push_back(RpcCall(jsonRPC, resultType));
1573 }
1574}
1575
1576PHD2::PHD2ResultType PHD2::takeRequestFromList(const QJsonObject &response)
1577{
1578 if (Q_UNLIKELY(!response.contains("id")))
1579 {
1580 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with no id";
1581 return NO_RESULT;
1582 }
1583
1584 int id = response["id"].toInt();
1585
1586 if (Q_UNLIKELY(id != pendingRpcId))
1587 {
1588 // RPC id mismatch -- this should never happen, something is
1589 // seriously wrong
1590 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with id" << id;
1591 return NO_RESULT;
1592 }
1593
1594 PHD2ResultType val = pendingRpcResultType;
1595 pendingRpcResultType = NO_RESULT;
1596 return val;
1597}
1598
1599}
Q_SCRIPTABLE Q_NOREPLY void setExposure(double value)
DBUS interface function.
Definition guide.cpp:1548
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:79
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void errorOccurred(QAbstractSocket::SocketError socketError)
QByteArray & append(QByteArrayView data)
char * data()
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
qsizetype size() const const
bool contains(const Key &key) const const
T value(const Key &key) const const
void readyRead()
bool empty() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QByteArray toJson(JsonFormat format) const const
bool contains(QLatin1StringView key) const const
iterator insert(QLatin1StringView key, const QJsonValue &value)
QString errorString() const const
const_reference at(qsizetype i) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool isEmpty() const const
QString number(double n, char format, int precision)
UniqueConnection
bool isActive() const const
void start()
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:59:51 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.