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 emit newLog(i18n("PHD2: SettleDone."));
404
405 // guiding stopped during dithering
406 if (state == PHD2::STOPPED)
407 return;
408
409 bool error = false;
410
411 if (jsonEvent["Status"].toInt() != 0)
412 {
413 error = true;
414 emit newLog(i18n("PHD2: Settling failed (%1).", jsonEvent["Error"].toString()));
415 }
416
417 bool wasDithering = isDitherActive;
418
419 isDitherActive = false;
420 isSettling = false;
421
422 if (wasDithering)
423 {
424 ditherTimer->stop();
425 if (error && Options::ditherFailAbortsAutoGuide())
426 {
427 abort();
428 emit newStatus(GUIDE_DITHERING_ERROR);
429 }
430 else
431 {
432 if (error)
433 emit newLog(i18n("PHD2: There was a dithering error, but continue guiding."));
434
435 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
436 }
437 }
438 else
439 {
440 if (error)
441 {
442 emit newLog(i18n("PHD2: Settling failed, aborted."));
443 emit newStatus(GUIDE_ABORTED);
444 }
445 else
446 {
447 // settle completed after "guide" command
448 emit newLog(i18n("PHD2: Settling complete, Guiding Started."));
449 emit newStatus(GUIDE_GUIDING);
450 }
451 }
452 }
453 break;
454
455 case StarSelected:
456 handlePHD2AppState(SELECTED);
457 break;
458
459 case StarLost:
460 // If we lost the guide star, let the state and abort timers update our state
461 handlePHD2AppState(LOSTLOCK);
462 break;
463
464 case GuidingStopped:
465 handlePHD2AppState(STOPPED);
466 break;
467
468 case Resumed:
469 handlePHD2AppState(GUIDING);
470 break;
471
472 case GuideStep:
473 {
474 // If we lost the guide star, let the state timer update our state
475 // Sometimes PHD2 is actually not guiding at that time, so we'll either resume or abort
476 if (state == LOSTLOCK)
477 emit newLog(i18n("PHD2: Star found, guiding is resuming..."));
478
479 if (isDitherActive)
480 return;
481
482 double diff_ra_pixels, diff_de_pixels, diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec, snr;
483 QString RADirection, DECDirection;
484 diff_ra_pixels = jsonEvent["RADistanceRaw"].toDouble();
485 diff_de_pixels = jsonEvent["DECDistanceRaw"].toDouble();
486 pulse_ra = jsonEvent["RADuration"].toDouble();
487 pulse_dec = jsonEvent["DECDuration"].toDouble();
488 RADirection = jsonEvent["RADirection"].toString();
489 DECDirection = jsonEvent["DECDirection"].toString();
490 snr = jsonEvent["SNR"].toDouble();
491
492 if (RADirection == "East")
493 pulse_ra = -pulse_ra; //West Direction is Positive, East is Negative
494 if (DECDirection == "South")
495 pulse_dec = -pulse_dec; //South Direction is Negative, North is Positive
496
497 //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.
498 if (pixelScale != 0)
499 {
500 diff_ra_arcsecs = diff_ra_pixels * pixelScale;
501 diff_de_arcsecs = diff_de_pixels * pixelScale;
502 }
503 else
504 {
505 diff_ra_arcsecs = 206.26480624709 * diff_ra_pixels * ccdPixelSizeX / mountFocalLength;
506 diff_de_arcsecs = 206.26480624709 * diff_de_pixels * ccdPixelSizeY / mountFocalLength;
507 }
508
509 if (std::isfinite(snr))
510 emit newSNR(snr);
511
512 if (std::isfinite(diff_ra_arcsecs) && std::isfinite(diff_de_arcsecs))
513 {
514 errorLog.append(QPointF(diff_ra_arcsecs, diff_de_arcsecs));
515 if(errorLog.size() > 50)
516 errorLog.remove(0);
517
518 emit newAxisDelta(diff_ra_arcsecs, diff_de_arcsecs);
519 emit newAxisPulse(pulse_ra, pulse_dec);
520
521 // Does PHD2 real a sky background or num-stars measure?
522 emit guideStats(diff_ra_arcsecs, diff_de_arcsecs, pulse_ra, pulse_dec,
523 std::isfinite(snr) ? snr : 0, 0, 0);
524
525 double total_sqr_RA_error = 0.0;
526 double total_sqr_DE_error = 0.0;
527
528 for (auto &point : errorLog)
529 {
530 total_sqr_RA_error += point.x() * point.x();
531 total_sqr_DE_error += point.y() * point.y();
532 }
533
534 emit newAxisSigma(sqrt(total_sqr_RA_error / errorLog.size()), sqrt(total_sqr_DE_error / errorLog.size()));
535
536 }
537 //Note that if it is receiving full size remote images, it should not get the guide star image.
538 //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
539 //If we are getting the full size image, we will want to know the lock position for the image that loads in the viewer.
540 if ( Options::guideSubframe() || currentCameraIsNotInEkos )
541 requestStarImage(32); //This requests a star image for the guide view. 32 x 32 pixels
542 else
543 requestLockPosition();
544 }
545 break;
546
547 case GuidingDithered:
548 break;
549
550 case LockPositionSet:
551 handlePHD2AppState(SELECTED);
552 break;
553
554 case LockPositionLost:
555 handlePHD2AppState(LOSTLOCK);
556 break;
557
558 case Alert:
559 emit newLog(i18n("PHD2 %1: %2", jsonEvent["Type"].toString(), jsonEvent["Msg"].toString()));
560 break;
561
562 case GuideParamChange:
563 case ConfigurationChange:
564 //Don't do anything for now, might change this later.
565 //Some Possible Parameter Names:
566 //Backlash comp enabled, Backlash comp amount,
567 //For Each Axis: MinMove, Max Duration,
568 //PPEC aggressiveness, PPEC prediction weight,
569 //Resist switch minimum motion, Resist switch aggression,
570 //Low-pass minimum move, Low-pass slope weight,
571 //Low-pass2 minimum move, Low-pass2 aggressiveness,
572 //Hysteresis hysteresis, Hysteresis aggression
573 break;
574
575 }
576}
577
578void PHD2::processPHD2State(const QString &phd2State)
579{
580 if (phd2State == "Stopped")
581 handlePHD2AppState(STOPPED);
582 else if (phd2State == "Selected")
583 handlePHD2AppState(SELECTED);
584 else if (phd2State == "Calibrating")
585 handlePHD2AppState(CALIBRATING);
586 else if (phd2State == "Guiding")
587 handlePHD2AppState(GUIDING);
588 else if (phd2State == "LostLock")
589 handlePHD2AppState(LOSTLOCK);
590 else if (phd2State == "Paused")
591 handlePHD2AppState(PAUSED);
592 else if (phd2State == "Looping")
593 handlePHD2AppState(LOOPING);
594 else emit newLog(QString("PHD2: Unsupported app state ") + phd2State + ".");
595}
596
597void PHD2::handlePHD2AppState(PHD2State newstate)
598{
599 // do not handle the same state twice
600 if (state == newstate)
601 return;
602
603 switch (newstate)
604 {
605 case STOPPED:
606 switch (state)
607 {
608 case CALIBRATING:
609 //emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString()));
610 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
611 break;
612 case LOOPING:
613 emit newLog(i18n("PHD2: Looping Exposures Stopped."));
614 emit newStatus(Ekos::GUIDE_IDLE);
615 break;
616 case GUIDING:
617 case LOSTLOCK:
618 emit newLog(i18n("PHD2: Guiding Stopped."));
619 emit newStatus(Ekos::GUIDE_ABORTED);
620 break;
621 default:
622 if (connection == DISCONNECTING)
623 {
624 emit newLog("PHD2: Disconnecting equipment and external guider...");
625 connectEquipment(false);
626 }
627 break;
628 }
629 break;
630
631 case SELECTED:
632 switch (state)
633 {
634 case STOPPED:
635 case CALIBRATING:
636 case GUIDING:
637 emit newLog(i18n("PHD2: Lock Position Set."));
638 if (isSettling)
639 {
640 newstate = CALIBRATING;
641 emit newStatus(Ekos::GUIDE_CALIBRATING);
642 }
643 break;
644 case DITHERING:
645 // do nothing, this is the initial step in PHD2 for dithering
646 break;
647 default:
648 emit newLog(i18n("PHD2: Star Selected."));
649 emit newStatus(GUIDE_STAR_SELECT);
650 }
651 break;
652
653 case GUIDING:
654 switch (state)
655 {
656 case DITHERING:
657 emit newLog(i18n("PHD2: Guiding...waiting for settle..."));
658 break;
659
660 case PAUSED:
661 emit newLog(i18n("PHD2: Dithering successful."));
662 abortTimer->stop();
663 ditherTimer->stop(); // stop immediately, since pausing could interrupt settling
664 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
665 break;
666 default:
667 emit newLog(i18n("PHD2: Guiding started."));
668 abortTimer->stop();
669 emit newStatus(Ekos::GUIDE_GUIDING);
670 break;
671 }
672 break;
673
674 case LOSTLOCK:
675 switch (state)
676 {
677 case CALIBRATING:
678 emit newLog(i18n("PHD2: Lock Position Lost, continuing calibration."));
679 // Don't be paranoid, accept star-lost events during calibration and trust PHD2 to complete
680 //newstate = STOPPED;
681 //emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
682 break;
683 case GUIDING:
684 emit newLog(i18n("PHD2: Star Lost. Trying to reacquire for %1s.", Options::guideLostStarTimeout()));
685 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000);
686 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout started (" << Options::guideLostStarTimeout() << " sec)";
687 emit newStatus(Ekos::GUIDE_REACQUIRE);
688 break;
689 default:
690 emit newLog(i18n("PHD2: Lock Position Lost."));
691 break;
692 }
693 break;
694
695 case PAUSED:
696 emit newLog(i18n("PHD2: Guiding paused."));
697 emit newStatus(GUIDE_SUSPENDED);
698 break;
699
700 case CALIBRATING:
701 emit newLog(i18n("PHD2: Calibrating, timing out in %1s.", Options::guideCalibrationTimeout()));
702 abortTimer->start(static_cast<int>(Options::guideCalibrationTimeout()) * 1000);
703 emit newStatus(GUIDE_CALIBRATING);
704 break;
705
706 case LOOPING:
707 switch (state)
708 {
709 case CALIBRATING:
710 emit newLog(i18n("PHD2: Calibration turned to looping, failed."));
711 emit newStatus(GUIDE_CALIBRATION_ERROR);
712 break;
713 default:
714 emit newLog(i18n("PHD2: Looping Exposures."));
715 emit newStatus(GUIDE_LOOPING);
716 break;
717 }
718 break;
719 case DITHERING:
720 emit newLog(i18n("PHD2: Dithering started."));
721 emit newStatus(GUIDE_DITHERING);
722 break;
723 }
724
725 state = newstate;
726}
727
728void PHD2::processPHD2Result(const QJsonObject &jsonObj, const QByteArray &line)
729{
730 PHD2ResultType resultType = takeRequestFromList(jsonObj);
731
732 if (resultType == STAR_IMAGE)
733 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received star image response, id" <<
734 jsonObj["id"].toInt(); // don't spam the log with image data
735 else
736 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: response:" << line;
737
738 switch (resultType)
739 {
740 case NO_RESULT:
741 //Ekos didn't ask for this result?
742 break;
743
744 case CAPTURE_SINGLE_FRAME: //capture_single_frame
745 break;
746
747 case CLEAR_CALIBRATION_COMMAND_RECEIVED: //clear_calibration
748 emit newLog(i18n("PHD2: Calibration is cleared"));
749 break;
750
751 case DITHER_COMMAND_RECEIVED: //dither
752 handlePHD2AppState(DITHERING);
753 break;
754
755 //find_star
756 //flip_calibration
757 //get_algo_param_names
758 //get_algo_param
759
760 case APP_STATE_RECEIVED: //get_app_state
761 {
762 QString state = jsonObj["State"].toString();
763 if (state.isEmpty())
764 state = jsonObj["result"].toString();
765 if (state.isEmpty())
766 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received unsupported app state";
767 else
768 processPHD2State(state);
769 }
770 break;
771
772 //get_calibrated
773 //get_calibration_data
774
775 case IS_EQUIPMENT_CONNECTED: //get_connected
776 {
777 bool isConnected = jsonObj["result"].toBool();
778 switch (connection)
779 {
780 case CONNECTING:
781 // We just plugged in server, request equipment connection if needed
782 if (isConnected)
783 {
784 connection = CONNECTED;
785 setEquipmentConnected();
786 }
787 else connectEquipment(true);
788 break;
789
790 case CONNECTED:
791 // We were waiting for equipment to be connected after plugging in server
792 if (isConnected)
793 setEquipmentConnected();
794 break;
795
796 case DISCONNECTING:
797 // We were waiting for equipment to be disconnected before unplugging from server
798 if (!isConnected)
799 {
800 connection = EQUIPMENT_DISCONNECTED;
801 Disconnect();
802 }
803 else connectEquipment(false);
804 break;
805
806 case EQUIPMENT_CONNECTED:
807 // Equipment was disconnected from PHD2 side, so notify clients and wait.
808 if (!isConnected)
809 {
810 // TODO: setEquipmentDisconnected()
811 connection = EQUIPMENT_DISCONNECTED;
812 emit newStatus(Ekos::GUIDE_DISCONNECTED);
813 }
814 break;
815
816 case DISCONNECTED:
817 case EQUIPMENT_DISCONNECTED:
818 // Equipment was connected from PHD2 side, so notify clients and wait.
819 if (isConnected)
820 setEquipmentConnected();
821 break;
822 }
823 }
824 break;
825
826 //get_cooler_status
827 case GET_CURRENT_EQUIPMENT: //get_current_equipment
828 {
829 QJsonObject equipObject = jsonObj["result"].toObject();
830 currentCamera = equipObject["camera"].toObject()["name"].toString();
831 currentMount = equipObject["mount"].toObject()["name"].toString();
832 currentAuxMount = equipObject["aux_mount"].toObject()["name"].toString();
833
834 emit guideEquipmentUpdated();
835
836 break;
837 }
838
839
840 case DEC_GUIDE_MODE: //get_dec_guide_mode
841 {
842 QString mode = jsonObj["result"].toString();
843 Ekos::Manager::Instance()->guideModule()->updateDirectionsFromPHD2(mode);
844 emit newLog(i18n("PHD2: DEC Guide Mode is Set to: %1", mode));
845 }
846 break;
847
848
849 case EXPOSURE_TIME: //get_exposure
850 {
851 int exposurems = jsonObj["result"].toInt();
852 double exposureTime = exposurems / 1000.0;
853 Ekos::Manager::Instance()->guideModule()->setExposure(exposureTime);
854 emit newLog(i18n("PHD2: Exposure Time set to: ") + QString::number(exposureTime, 'f', 2));
855 break;
856 }
857
858
859 case EXPOSURE_DURATIONS: //get_exposure_durations
860 {
861 QVariantList exposureListArray = jsonObj["result"].toArray().toVariantList();
862 logValidExposureTimes = i18n("PHD2: Valid Exposure Times: Auto, ");
863 QList<double> values;
864 for(int i = 1; i < exposureListArray.size();
865 i ++) //For some reason PHD2 has a negative exposure time of 1 at the start of the array?
866 values << exposureListArray.at(i).toDouble() / 1000.0; //PHD2 reports in ms.
867 logValidExposureTimes += Ekos::Manager::Instance()->guideModule()->setRecommendedExposureValues(values);
868 emit newLog(logValidExposureTimes);
869 break;
870 }
871 case LOCK_POSITION: //get_lock_position
872 {
873 if(jsonObj["result"].toArray().count() == 2)
874 {
875 double x = jsonObj["result"].toArray().at(0).toDouble();
876 double y = jsonObj["result"].toArray().at(1).toDouble();
877 QVector3D newStarCenter(x, y, 0);
878 emit newStarPosition(newStarCenter, true);
879
880 //This is needed so that PHD2 sends the new star pixmap when
881 //remote images are enabled.
882 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap());
883 }
884 break;
885 }
886 //get_lock_shift_enabled
887 //get_lock_shift_params
888 //get_paused
889
890 case PIXEL_SCALE: //get_pixel_scale
891 pixelScale = jsonObj["result"].toDouble();
892 if (pixelScale == 0)
893 emit newLog(i18n("PHD2: Please set CCD and telescope parameters in PHD2, Pixel Scale is invalid."));
894 else
895 emit newLog(i18n("PHD2: Pixel Scale is %1 arcsec per pixel", QString::number(pixelScale, 'f', 2)));
896 break;
897
898 //get_profile
899 //get_profiles
900 //get_search_region
901 //get_sensor_temperature
902
903 case STAR_IMAGE: //get_star_image
904 {
905 starImageRequested = false;
906 QJsonObject jsonResult = jsonObj["result"].toObject();
907 processStarImage(jsonResult);
908 break;
909 }
910
911 //get_use_subframes
912
913 case GUIDE_COMMAND_RECEIVED: //guide
914 if (0 != jsonObj["result"].toInt(0))
915 {
916 emit newLog("PHD2: Guide command was rejected.");
917 handlePHD2AppState(STOPPED);
918 }
919 break;
920
921 //guide_pulse
922
923 case LOOP: //loop
924 handlePHD2AppState(jsonObj["result"].toBool() ? LOOPING : STOPPED);
925 break;
926
927 //save_image
928 //set_algo_param
929
930 case CONNECTION_RESULT: //set_connected
931 checkIfEquipmentConnected();
932 break;
933
934 case SET_DEC_GUIDE_MODE_COMMAND_RECEIVED: //set_dec_guide_mode
935 checkDEGuideMode();
936 break;
937
938 case SET_EXPOSURE_COMMAND_RECEIVED: //set_exposure
939 requestExposureTime(); //This will check what it was set to and print a message as to what it is.
940 break;
941
942 case SET_LOCK_POSITION: //set_lock_position
943 handlePHD2AppState(SELECTED);
944 break;
945
946 //set_lock_shift_enabled
947 //set_lock_shift_params
948
949 case SET_PAUSED_COMMAND_RECEIVED: //set_paused
950 handlePHD2AppState(PAUSED);
951 break;
952 //set_profile
953 //shutdown
954
955 case STOP_CAPTURE_COMMAND_RECEIVED: //stop_capture
956 handlePHD2AppState(STOPPED);
957 //emit newStatus(GUIDE_ABORTED);
958 break;
959 }
960
961 // send the next pending call
962 sendNextRpcCall();
963}
964
965void PHD2::processPHD2Error(const QJsonObject &jsonError, const QByteArray &line)
966{
967 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: error:" << line;
968
969 QJsonObject jsonErrorObject = jsonError["error"].toObject();
970
971 PHD2ResultType resultType = takeRequestFromList(jsonError);
972
973 // This means the user mistakenly entered an invalid exposure time.
974 switch (resultType)
975 {
976 case SET_EXPOSURE_COMMAND_RECEIVED:
977 emit newLog(logValidExposureTimes); //This will let the user know the valid exposure durations
978 QTimer::singleShot(300, [ = ] {requestExposureTime();}); //This will reset the Exposure time in Ekos to PHD2's current exposure time after a third of a second.
979 break;
980
981 case CONNECTION_RESULT:
982 connection = EQUIPMENT_DISCONNECTED;
983 emit newStatus(Ekos::GUIDE_DISCONNECTED);
984 break;
985
986 case DITHER_COMMAND_RECEIVED:
987 ditherTimer->stop();
988 isSettling = false;
989 isDitherActive = false;
990 emit newStatus(GUIDE_DITHERING_ERROR);
991
992 if (Options::ditherFailAbortsAutoGuide())
993 {
994 abort();
995 emit newLog("PHD2: failing after dithering aborts.");
996 emit newStatus(GUIDE_ABORTED);
997 }
998 else
999 {
1000 // !FIXME-ag why is this trying to resume (un-pause)?
1001 resume();
1002 }
1003 break;
1004
1005 case GUIDE_COMMAND_RECEIVED:
1006 isSettling = false;
1007 break;
1008
1009 default:
1010 emit newLog(i18n("PHD2 Error: unhandled '%1'", jsonErrorObject["message"].toString()));
1011 break;
1012 }
1013
1014 // send the next pending call
1015 sendNextRpcCall();
1016}
1017
1018//These methods process the Star Images the PHD2 provides
1019
1020void PHD2::setGuideView(const QSharedPointer<FITSView> &guideView)
1021{
1022 m_GuideFrame = guideView;
1023}
1024
1025void PHD2::processStarImage(const QJsonObject &jsonStarFrame)
1026{
1027 //The width and height of the received PHD2 Star Image
1028 int width = jsonStarFrame["width"].toInt();
1029 int height = jsonStarFrame["height"].toInt();
1030
1031 //This section sets up the FITS File
1032 fitsfile *fptr = nullptr;
1033 int status = 0;
1034 long fpixel = 1, naxis = 2, nelements, exposure;
1035 long naxes[2] = { width, height };
1036 char error_status[512] = {0};
1037
1038 void* fits_buffer = nullptr;
1039 size_t fits_buffer_size = 0;
1040 if (fits_create_memfile(&fptr, &fits_buffer, &fits_buffer_size, 4096, realloc, &status))
1041 {
1042 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_file failed:" << error_status;
1043 return;
1044 }
1045
1046 if (fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status))
1047 {
1048 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_img failed:" << error_status;
1049 status = 0;
1050 fits_close_file(fptr, &status);
1051 free(fits_buffer);
1052 return;
1053 }
1054
1055 //Note, this is made up. If you want the actual exposure time, you have to request it from PHD2
1056 exposure = 1;
1057 fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status);
1058
1059 //This section takes the Pixels from the JSON Document
1060 //Then it converts from base64 to a QByteArray
1061 //Then it creates a datastream from the QByteArray to the pixel array for the FITS File
1062 QByteArray converted = QByteArray::fromBase64(jsonStarFrame["pixels"].toString().toLocal8Bit());
1063
1064 //This finishes up and closes the FITS file
1065 nelements = naxes[0] * naxes[1];
1066 if (fits_write_img(fptr, TUSHORT, fpixel, nelements, converted.data(), &status))
1067 {
1068 fits_get_errstatus(status, error_status);
1069 qCWarning(KSTARS_EKOS_GUIDE) << "fits_write_img failed:" << error_status;
1070 status = 0;
1071 fits_close_file(fptr, &status);
1072 free(fits_buffer);
1073 return;
1074 }
1075
1076 if (fits_flush_file(fptr, &status))
1077 {
1078 fits_get_errstatus(status, error_status);
1079 qCWarning(KSTARS_EKOS_GUIDE) << "fits_flush_file failed:" << error_status;
1080 status = 0;
1081 fits_close_file(fptr, &status);
1082 free(fits_buffer);
1083 return;
1084 }
1085
1086 if (fits_close_file(fptr, &status))
1087 {
1088 fits_get_errstatus(status, error_status);
1089 qCWarning(KSTARS_EKOS_GUIDE) << "fits_close_file failed:" << error_status;
1090 free(fits_buffer);
1091 return;
1092 }
1093
1094 //This loads the FITS file in the Guide FITSView
1095 //Then it updates the Summary Screen
1097 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(fits_buffer), fits_buffer_size);
1098 fdata.reset(new FITSData(), &QObject::deleteLater);
1099 fdata->setExtension(QString("fits"));
1100 fdata->loadFromBuffer(buffer);
1101 free(fits_buffer);
1102 m_GuideFrame->loadData(fdata);
1103
1104 m_GuideFrame->updateFrame();
1105 m_GuideFrame->setTrackingBox(QRect(0, 0, width, height));
1106 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap());
1107}
1108
1109void PHD2::setEquipmentConnected()
1110{
1111 if (connection != EQUIPMENT_CONNECTED)
1112 {
1113 setConnectedRetries = 0;
1114 connection = EQUIPMENT_CONNECTED;
1115 emit newStatus(Ekos::GUIDE_CONNECTED);
1116 updateGuideParameters();
1117 requestExposureDurations();
1118 requestCurrentEquipmentUpdate();
1119 }
1120}
1121
1122void PHD2::updateGuideParameters()
1123{
1124 if (pixelScale == 0)
1125 requestPixelScale();
1126 requestExposureTime();
1127 checkDEGuideMode();
1128}
1129
1130//This section handles the methods/requests sent to PHD2, some are not implemented.
1131
1132//capture_single_frame
1133void PHD2::captureSingleFrame()
1134{
1135 sendPHD2Request("capture_single_frame");
1136}
1137
1138//clear_calibration
1139bool PHD2::clearCalibration()
1140{
1141 if (connection != EQUIPMENT_CONNECTED)
1142 {
1143 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1144 emit newStatus(Ekos::GUIDE_ABORTED);
1145 return false;
1146 }
1147
1148 QJsonArray args;
1149 //This instructs PHD2 which calibration to clear.
1150 args << "mount";
1151 sendPHD2Request("clear_calibration", args);
1152
1153 return true;
1154}
1155
1156//dither
1157bool PHD2::dither(double pixels)
1158{
1159 if (connection != EQUIPMENT_CONNECTED)
1160 {
1161 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1162 emit newStatus(Ekos::GUIDE_ABORTED);
1163 return false;
1164 }
1165
1166 if (isSettling)
1167 {
1168 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring dither requested while already settling";
1169
1170 if (!isDitherActive)
1171 {
1172 // act like we just dithered so we get the appropriate
1173 // effects after the settling completes
1174 handlePHD2AppState(DITHERING);
1175 isDitherActive = true;
1176 }
1177 return true;
1178 }
1179
1180 QJsonArray args;
1181 QJsonObject settle;
1182
1183 int ditherTimeout = static_cast<int>(Options::ditherTimeout());
1184
1185 settle.insert("pixels", static_cast<double>(Options::ditherThreshold()));
1186 settle.insert("time", static_cast<int>(Options::ditherSettle()));
1187 settle.insert("timeout", ditherTimeout);
1188
1189 // Pixels
1190 args << pixels;
1191 // RA Only?
1192 args << false;
1193 // Settle
1194 args << settle;
1195
1196 isSettling = true;
1197 isDitherActive = true;
1198
1199 // PHD2 will send a SettleDone event shortly after the settling
1200 // timeout in PHD2. We don't really need a timer here, but we'll
1201 // set one anyway (belt and suspenders). Make sure to give an
1202 // extra time allowance since PHD2 won't report its timeout until
1203 // the completion of the next guide exposure after the timeout
1204 // period expires.
1205 enum { TIMEOUT_EXTRA_SECONDS = 60 }; // at least as long as any reasonable guide exposure
1206 int millis = (ditherTimeout + TIMEOUT_EXTRA_SECONDS) * 1000;
1207 ditherTimer->start(millis);
1208
1209 sendPHD2Request("dither", args);
1210
1211 handlePHD2AppState(DITHERING);
1212
1213 return true;
1214}
1215
1216//find_star
1217//flip_calibration
1218//get_algo_param_names
1219//get_algo_param
1220
1221//get_app_state
1222void PHD2::requestAppState()
1223{
1224 sendPHD2Request("get_app_state");
1225}
1226
1227//get_calibrated
1228//get_calibration_data
1229
1230//get_connected
1231void PHD2::checkIfEquipmentConnected()
1232{
1233 sendPHD2Request("get_connected");
1234}
1235
1236//get_cooler_status
1237//get_current_equipment
1238void PHD2::requestCurrentEquipmentUpdate()
1239{
1240 sendPHD2Request("get_current_equipment");
1241}
1242
1243//get_dec_guide_mode
1244void PHD2::checkDEGuideMode()
1245{
1246 sendPHD2Request("get_dec_guide_mode");
1247}
1248
1249//get_exposure
1250void PHD2::requestExposureTime()
1251{
1252 sendPHD2Request("get_exposure");
1253}
1254
1255//get_exposure_durations
1256void PHD2::requestExposureDurations()
1257{
1258 sendPHD2Request("get_exposure_durations");
1259}
1260
1261//get_lock_position
1262void PHD2::requestLockPosition()
1263{
1264 sendPHD2Request("get_lock_position");
1265}
1266//get_lock_shift_enabled
1267//get_lock_shift_params
1268//get_paused
1269
1270//get_pixel_scale
1271void PHD2::requestPixelScale()
1272{
1273 sendPHD2Request("get_pixel_scale");
1274}
1275
1276//get_profile
1277//get_profiles
1278//get_search_region
1279//get_sensor_temperature
1280
1281//get_star_image
1282void PHD2::requestStarImage(int size)
1283{
1284 if (starImageRequested)
1285 {
1286 if (Options::verboseLogging())
1287 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: skip extra star image request";
1288 return;
1289 }
1290
1291 QJsonArray args2;
1292 args2 << size; // This is both the width and height.
1293 sendPHD2Request("get_star_image", args2);
1294
1295 starImageRequested = true;
1296}
1297
1298//get_use_subframes
1299
1300//guide
1301bool PHD2::guide()
1302{
1303 if (state == GUIDING)
1304 {
1305 emit newLog(i18n("PHD2: Guiding is already running."));
1306 emit newStatus(Ekos::GUIDE_GUIDING);
1307 return true;
1308 }
1309
1310 if (connection != EQUIPMENT_CONNECTED)
1311 {
1312 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1313 emit newStatus(Ekos::GUIDE_ABORTED);
1314 return false;
1315 }
1316
1317 QJsonArray args;
1318 QJsonObject settle;
1319
1320 settle.insert("pixels", static_cast<double>(Options::ditherThreshold()));
1321 settle.insert("time", static_cast<int>(Options::ditherSettle()));
1322 settle.insert("timeout", static_cast<int>(Options::ditherTimeout()));
1323
1324 // Settle param
1325 args << settle;
1326 // Recalibrate param
1327 args << false;
1328
1329 errorLog.clear();
1330
1331 isSettling = true;
1332 sendPHD2Request("guide", args);
1333
1334 return true;
1335}
1336
1337//guide_pulse
1338//loop
1339void PHD2::loop()
1340{
1341 sendPHD2Request("loop");
1342}
1343//save_image
1344//set_algo_param
1345
1346//set_connected
1347void PHD2::connectEquipment(bool enable)
1348{
1349 if (connection == EQUIPMENT_CONNECTED && enable == true)
1350 return;
1351
1352 if (connection == EQUIPMENT_DISCONNECTED && enable == false)
1353 return;
1354
1355 if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES)
1356 {
1357 setConnectedRetries = 0;
1358 connection = EQUIPMENT_DISCONNECTED;
1359 emit newStatus(Ekos::GUIDE_DISCONNECTED);
1360 return;
1361 }
1362
1363 pixelScale = 0 ;
1364
1365 QJsonArray args;
1366
1367 // connected = enable
1368 args << enable;
1369
1370 if (enable)
1371 emit newLog(i18n("PHD2: Connecting Equipment. . ."));
1372 else
1373 emit newLog(i18n("PHD2: Disconnecting Equipment. . ."));
1374
1375 sendPHD2Request("set_connected", args);
1376}
1377
1378//set_dec_guide_mode
1379void PHD2::requestSetDEGuideMode(bool deEnabled, bool nEnabled,
1380 bool sEnabled) //Possible Settings Off, Auto, North, and South
1381{
1382 QJsonArray args;
1383
1384 if(deEnabled)
1385 {
1386 if(nEnabled && sEnabled)
1387 args << "Auto";
1388 else if(nEnabled)
1389 args << "North";
1390 else if(sEnabled)
1391 args << "South";
1392 else
1393 args << "Off";
1394 }
1395 else
1396 {
1397 args << "Off";
1398 }
1399
1400 sendPHD2Request("set_dec_guide_mode", args);
1401}
1402
1403//set_exposure
1404void PHD2::requestSetExposureTime(int time) //Note: time is in milliseconds
1405{
1406 QJsonArray args;
1407 args << time;
1408 sendPHD2Request("set_exposure", args);
1409}
1410
1411//set_lock_position
1412void PHD2::setLockPosition(double x, double y)
1413{
1414 // Note: false will mean if a guide star is near the coordinates, it will use that.
1415 QJsonArray args;
1416 args << x << y << false;
1417 sendPHD2Request("set_lock_position", args);
1418}
1419//set_lock_shift_enabled
1420//set_lock_shift_params
1421
1422//set_paused
1423bool PHD2::suspend()
1424{
1425 if (connection != EQUIPMENT_CONNECTED)
1426 {
1427 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1428 emit newStatus(Ekos::GUIDE_ABORTED);
1429 return false;
1430 }
1431
1432 QJsonArray args;
1433
1434 // Paused param
1435 args << true;
1436 // FULL param
1437 args << "full";
1438
1439 sendPHD2Request("set_paused", args);
1440
1441 if (abortTimer->isActive())
1442 {
1443 // Avoid that the a preceding lost star event leads to an abort while guiding is suspended.
1444 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout cancelled.";
1445 abortTimer->stop();
1446 }
1447
1448 return true;
1449}
1450
1451//set_paused (also)
1452bool PHD2::resume()
1453{
1454 if (connection != EQUIPMENT_CONNECTED)
1455 {
1456 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1457 emit newStatus(Ekos::GUIDE_ABORTED);
1458 return false;
1459 }
1460
1461 QJsonArray args;
1462
1463 // Paused param
1464 args << false;
1465
1466 sendPHD2Request("set_paused", args);
1467
1468 if (state == LOSTLOCK)
1469 {
1470 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout restarted.";
1471 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000);
1472 }
1473
1474 return true;
1475}
1476
1477//set_profile
1478//shutdown
1479
1480//stop_capture
1481bool PHD2::abort()
1482{
1483 if (connection != EQUIPMENT_CONNECTED)
1484 {
1485 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1486 emit newStatus(Ekos::GUIDE_ABORTED);
1487 return false;
1488 }
1489
1490 abortTimer->stop();
1491
1492 sendPHD2Request("stop_capture");
1493 return true;
1494}
1495
1496//This method is not handled by PHD2
1497bool PHD2::calibrate()
1498{
1499 // We don't explicitly do calibration since it is done in the guide step by PHD2 anyway
1500 //emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
1501 return true;
1502}
1503
1504//This is how information requests and commands for PHD2 are handled
1505
1506void PHD2::sendRpcCall(QJsonObject &call, PHD2ResultType resultType)
1507{
1508 assert(resultType != NO_RESULT); // should be a real request
1509 assert(pendingRpcResultType == NO_RESULT); // only one pending RPC call at a time
1510
1511 if (tcpSocket->state() == QTcpSocket::ConnectedState)
1512 {
1513 int rpcId = nextRpcId++;
1514 call.insert("id", rpcId);
1515
1517
1518 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: request:" << request;
1519
1520 request.append("\r\n");
1521
1522 qint64 const n = tcpSocket->write(request);
1523
1524 if ((int) n == request.size())
1525 {
1526 // RPC call succeeded, remember ID and expected result type
1527 pendingRpcId = rpcId;
1528 pendingRpcResultType = resultType;
1529 }
1530 else
1531 {
1532 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: unexpected short write:" << n << "bytes of" << request.size();
1533 }
1534 }
1535}
1536
1537void PHD2::sendNextRpcCall()
1538{
1539 if (pendingRpcResultType != NO_RESULT)
1540 return; // a request is currently outstanding
1541
1542 if (rpcRequestQueue.empty())
1543 return; // no queued requests
1544
1545 RpcCall &call = rpcRequestQueue.front();
1546 sendRpcCall(call.call, call.resultType);
1547 rpcRequestQueue.pop_front();
1548}
1549
1550void PHD2::sendPHD2Request(const QString &method, const QJsonArray &args)
1551{
1552 assert(methodResults.contains(method));
1553
1554 PHD2ResultType resultType = methodResults[method];
1555
1556 QJsonObject jsonRPC;
1557
1558 jsonRPC.insert("jsonrpc", "2.0");
1559 jsonRPC.insert("method", method);
1560
1561 if (!args.empty())
1562 jsonRPC.insert("params", args);
1563
1564 if (pendingRpcResultType == NO_RESULT)
1565 {
1566 // no outstanding rpc call, send it right off
1567 sendRpcCall(jsonRPC, resultType);
1568 }
1569 else
1570 {
1571 // there is already an outstanding call, enqueue this call
1572 // until the prior call completes
1573
1574 if (Options::verboseLogging())
1575 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: defer call" << method;
1576
1577 rpcRequestQueue.push_back(RpcCall(jsonRPC, resultType));
1578 }
1579}
1580
1581PHD2::PHD2ResultType PHD2::takeRequestFromList(const QJsonObject &response)
1582{
1583 if (Q_UNLIKELY(!response.contains("id")))
1584 {
1585 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with no id";
1586 return NO_RESULT;
1587 }
1588
1589 int id = response["id"].toInt();
1590
1591 if (Q_UNLIKELY(id != pendingRpcId))
1592 {
1593 // RPC id mismatch -- this should never happen, something is
1594 // seriously wrong
1595 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with id" << id;
1596 return NO_RESULT;
1597 }
1598
1599 PHD2ResultType val = pendingRpcResultType;
1600 pendingRpcResultType = NO_RESULT;
1601 return val;
1602}
1603
1604}
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:83
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 Sat Dec 21 2024 17:04:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.