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
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
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
303
305
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
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?
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 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
659 break;
660 default:
661 emit newLog(i18n("PHD2: Guiding started."));
662 abortTimer->stop();
663 emit newStatus(Ekos::GUIDE_GUIDING);
664 break;
665 }
666 break;
667
668 case LOSTLOCK:
669 switch (state)
670 {
671 case CALIBRATING:
672 emit newLog(i18n("PHD2: Lock Position Lost, continuing calibration."));
673 // Don't be paranoid, accept star-lost events during calibration and trust PHD2 to complete
674 //newstate = STOPPED;
675 //emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
676 break;
677 case GUIDING:
678 emit newLog(i18n("PHD2: Star Lost. Trying to reacquire for %1s.", Options::guideLostStarTimeout()));
679 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000);
680 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout started (" << Options::guideLostStarTimeout() << " sec)";
681 emit newStatus(Ekos::GUIDE_REACQUIRE);
682 break;
683 default:
684 emit newLog(i18n("PHD2: Lock Position Lost."));
685 break;
686 }
687 break;
688
689 case PAUSED:
690 emit newLog(i18n("PHD2: Guiding paused."));
691 emit newStatus(GUIDE_SUSPENDED);
692 break;
693
694 case CALIBRATING:
695 emit newLog(i18n("PHD2: Calibrating, timing out in %1s.", Options::guideCalibrationTimeout()));
696 abortTimer->start(static_cast<int>(Options::guideCalibrationTimeout()) * 1000);
697 emit newStatus(GUIDE_CALIBRATING);
698 break;
699
700 case LOOPING:
701 switch (state)
702 {
703 case CALIBRATING:
704 emit newLog(i18n("PHD2: Calibration turned to looping, failed."));
705 emit newStatus(GUIDE_CALIBRATION_ERROR);
706 break;
707 default:
708 emit newLog(i18n("PHD2: Looping Exposures."));
709 emit newStatus(GUIDE_LOOPING);
710 break;
711 }
712 break;
713 case DITHERING:
714 emit newLog(i18n("PHD2: Dithering started."));
715 emit newStatus(GUIDE_DITHERING);
716 break;
717 }
718
719 state = newstate;
720}
721
722void PHD2::processPHD2Result(const QJsonObject &jsonObj, const QByteArray &line)
723{
724 PHD2ResultType resultType = takeRequestFromList(jsonObj);
725
726 if (resultType == STAR_IMAGE)
727 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received star image response, id" <<
728 jsonObj["id"].toInt(); // don't spam the log with image data
729 else
730 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: response:" << line;
731
732 switch (resultType)
733 {
734 case NO_RESULT:
735 //Ekos didn't ask for this result?
736 break;
737
738 case CAPTURE_SINGLE_FRAME: //capture_single_frame
739 break;
740
741 case CLEAR_CALIBRATION_COMMAND_RECEIVED: //clear_calibration
742 emit newLog(i18n("PHD2: Calibration is cleared"));
743 break;
744
745 case DITHER_COMMAND_RECEIVED: //dither
746 handlePHD2AppState(DITHERING);
747 break;
748
749 //find_star
750 //flip_calibration
751 //get_algo_param_names
752 //get_algo_param
753
754 case APP_STATE_RECEIVED: //get_app_state
755 {
756 QString state = jsonObj["State"].toString();
757 if (state.isEmpty())
758 state = jsonObj["result"].toString();
759 if (state.isEmpty())
760 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: received unsupported app state";
761 else
762 processPHD2State(state);
763 }
764 break;
765
766 //get_calibrated
767 //get_calibration_data
768
769 case IS_EQUIPMENT_CONNECTED: //get_connected
770 {
771 bool isConnected = jsonObj["result"].toBool();
772 switch (connection)
773 {
774 case CONNECTING:
775 // We just plugged in server, request equipment connection if needed
776 if (isConnected)
777 {
778 connection = CONNECTED;
779 setEquipmentConnected();
780 }
781 else connectEquipment(true);
782 break;
783
784 case CONNECTED:
785 // We were waiting for equipment to be connected after plugging in server
786 if (isConnected)
787 setEquipmentConnected();
788 break;
789
790 case DISCONNECTING:
791 // We were waiting for equipment to be disconnected before unplugging from server
792 if (!isConnected)
793 {
794 connection = EQUIPMENT_DISCONNECTED;
795 Disconnect();
796 }
797 else connectEquipment(false);
798 break;
799
800 case EQUIPMENT_CONNECTED:
801 // Equipment was disconnected from PHD2 side, so notify clients and wait.
802 if (!isConnected)
803 {
804 // TODO: setEquipmentDisconnected()
805 connection = EQUIPMENT_DISCONNECTED;
806 emit newStatus(Ekos::GUIDE_DISCONNECTED);
807 }
808 break;
809
810 case DISCONNECTED:
811 case EQUIPMENT_DISCONNECTED:
812 // Equipment was connected from PHD2 side, so notify clients and wait.
813 if (isConnected)
814 setEquipmentConnected();
815 break;
816 }
817 }
818 break;
819
820 //get_cooler_status
821 case GET_CURRENT_EQUIPMENT: //get_current_equipment
822 {
823 QJsonObject equipObject = jsonObj["result"].toObject();
824 currentCamera = equipObject["camera"].toObject()["name"].toString();
825 currentMount = equipObject["mount"].toObject()["name"].toString();
826 currentAuxMount = equipObject["aux_mount"].toObject()["name"].toString();
827
828 emit guideEquipmentUpdated();
829
830 break;
831 }
832
833
834 case DEC_GUIDE_MODE: //get_dec_guide_mode
835 {
836 QString mode = jsonObj["result"].toString();
837 Ekos::Manager::Instance()->guideModule()->updateDirectionsFromPHD2(mode);
838 emit newLog(i18n("PHD2: DEC Guide Mode is Set to: %1", mode));
839 }
840 break;
841
842
843 case EXPOSURE_TIME: //get_exposure
844 {
845 int exposurems = jsonObj["result"].toInt();
846 double exposureTime = exposurems / 1000.0;
847 Ekos::Manager::Instance()->guideModule()->setExposure(exposureTime);
848 emit newLog(i18n("PHD2: Exposure Time set to: ") + QString::number(exposureTime, 'f', 2));
849 break;
850 }
851
852
853 case EXPOSURE_DURATIONS: //get_exposure_durations
854 {
855 QVariantList exposureListArray = jsonObj["result"].toArray().toVariantList();
856 logValidExposureTimes = i18n("PHD2: Valid Exposure Times: Auto, ");
857 QList<double> values;
858 for(int i = 1; i < exposureListArray.size();
859 i ++) //For some reason PHD2 has a negative exposure time of 1 at the start of the array?
860 values << exposureListArray.at(i).toDouble() / 1000.0; //PHD2 reports in ms.
861 logValidExposureTimes += Ekos::Manager::Instance()->guideModule()->setRecommendedExposureValues(values);
862 emit newLog(logValidExposureTimes);
863 break;
864 }
865 case LOCK_POSITION: //get_lock_position
866 {
867 if(jsonObj["result"].toArray().count() == 2)
868 {
869 double x = jsonObj["result"].toArray().at(0).toDouble();
870 double y = jsonObj["result"].toArray().at(1).toDouble();
871 QVector3D newStarCenter(x, y, 0);
872 emit newStarPosition(newStarCenter, true);
873
874 //This is needed so that PHD2 sends the new star pixmap when
875 //remote images are enabled.
876 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap());
877 }
878 break;
879 }
880 //get_lock_shift_enabled
881 //get_lock_shift_params
882 //get_paused
883
884 case PIXEL_SCALE: //get_pixel_scale
885 pixelScale = jsonObj["result"].toDouble();
886 if (pixelScale == 0)
887 emit newLog(i18n("PHD2: Please set CCD and telescope parameters in PHD2, Pixel Scale is invalid."));
888 else
889 emit newLog(i18n("PHD2: Pixel Scale is %1 arcsec per pixel", QString::number(pixelScale, 'f', 2)));
890 break;
891
892 //get_profile
893 //get_profiles
894 //get_search_region
895 //get_sensor_temperature
896
897 case STAR_IMAGE: //get_star_image
898 {
899 starImageRequested = false;
900 QJsonObject jsonResult = jsonObj["result"].toObject();
901 processStarImage(jsonResult);
902 break;
903 }
904
905 //get_use_subframes
906
907 case GUIDE_COMMAND_RECEIVED: //guide
908 if (0 != jsonObj["result"].toInt(0))
909 {
910 emit newLog("PHD2: Guide command was rejected.");
911 handlePHD2AppState(STOPPED);
912 }
913 break;
914
915 //guide_pulse
916
917 case LOOP: //loop
918 handlePHD2AppState(jsonObj["result"].toBool() ? LOOPING : STOPPED);
919 break;
920
921 //save_image
922 //set_algo_param
923
924 case CONNECTION_RESULT: //set_connected
925 checkIfEquipmentConnected();
926 break;
927
928 case SET_DEC_GUIDE_MODE_COMMAND_RECEIVED: //set_dec_guide_mode
929 checkDEGuideMode();
930 break;
931
932 case SET_EXPOSURE_COMMAND_RECEIVED: //set_exposure
933 requestExposureTime(); //This will check what it was set to and print a message as to what it is.
934 break;
935
936 case SET_LOCK_POSITION: //set_lock_position
937 handlePHD2AppState(SELECTED);
938 break;
939
940 //set_lock_shift_enabled
941 //set_lock_shift_params
942
943 case SET_PAUSED_COMMAND_RECEIVED: //set_paused
944 handlePHD2AppState(PAUSED);
945 break;
946 //set_profile
947 //shutdown
948
949 case STOP_CAPTURE_COMMAND_RECEIVED: //stop_capture
950 handlePHD2AppState(STOPPED);
951 //emit newStatus(GUIDE_ABORTED);
952 break;
953 }
954
955 // send the next pending call
956 sendNextRpcCall();
957}
958
959void PHD2::processPHD2Error(const QJsonObject &jsonError, const QByteArray &line)
960{
961 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: error:" << line;
962
963 QJsonObject jsonErrorObject = jsonError["error"].toObject();
964
965 PHD2ResultType resultType = takeRequestFromList(jsonError);
966
967 // This means the user mistakenly entered an invalid exposure time.
968 switch (resultType)
969 {
970 case SET_EXPOSURE_COMMAND_RECEIVED:
971 emit newLog(logValidExposureTimes); //This will let the user know the valid exposure durations
972 QTimer::singleShot(300, [ = ] {requestExposureTime();}); //This will reset the Exposure time in Ekos to PHD2's current exposure time after a third of a second.
973 break;
974
975 case CONNECTION_RESULT:
976 connection = EQUIPMENT_DISCONNECTED;
977 emit newStatus(Ekos::GUIDE_DISCONNECTED);
978 break;
979
980 case DITHER_COMMAND_RECEIVED:
981 ditherTimer->stop();
982 isSettling = false;
983 isDitherActive = false;
984 emit newStatus(GUIDE_DITHERING_ERROR);
985
986 if (Options::ditherFailAbortsAutoGuide())
987 {
988 abort();
989 emit newLog("PHD2: failing after dithering aborts.");
990 emit newStatus(GUIDE_ABORTED);
991 }
992 else
993 {
994 // !FIXME-ag why is this trying to resume (un-pause)?
995 resume();
996 }
997 break;
998
999 case GUIDE_COMMAND_RECEIVED:
1000 isSettling = false;
1001 break;
1002
1003 default:
1004 emit newLog(i18n("PHD2 Error: unhandled '%1'", jsonErrorObject["message"].toString()));
1005 break;
1006 }
1007
1008 // send the next pending call
1009 sendNextRpcCall();
1010}
1011
1012//These methods process the Star Images the PHD2 provides
1013
1014void PHD2::setGuideView(const QSharedPointer<FITSView> &guideView)
1015{
1016 m_GuideFrame = guideView;
1017}
1018
1019void PHD2::processStarImage(const QJsonObject &jsonStarFrame)
1020{
1021 //The width and height of the received PHD2 Star Image
1022 int width = jsonStarFrame["width"].toInt();
1023 int height = jsonStarFrame["height"].toInt();
1024
1025 //This section sets up the FITS File
1026 fitsfile *fptr = nullptr;
1027 int status = 0;
1028 long fpixel = 1, naxis = 2, nelements, exposure;
1029 long naxes[2] = { width, height };
1030 char error_status[512] = {0};
1031
1032 void* fits_buffer = nullptr;
1033 size_t fits_buffer_size = 0;
1034 if (fits_create_memfile(&fptr, &fits_buffer, &fits_buffer_size, 4096, realloc, &status))
1035 {
1036 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_file failed:" << error_status;
1037 return;
1038 }
1039
1040 if (fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status))
1041 {
1042 qCWarning(KSTARS_EKOS_GUIDE) << "fits_create_img failed:" << error_status;
1043 status = 0;
1044 fits_close_file(fptr, &status);
1045 free(fits_buffer);
1046 return;
1047 }
1048
1049 //Note, this is made up. If you want the actual exposure time, you have to request it from PHD2
1050 exposure = 1;
1051 fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status);
1052
1053 //This section takes the Pixels from the JSON Document
1054 //Then it converts from base64 to a QByteArray
1055 //Then it creates a datastream from the QByteArray to the pixel array for the FITS File
1056 QByteArray converted = QByteArray::fromBase64(jsonStarFrame["pixels"].toString().toLocal8Bit());
1057
1058 //This finishes up and closes the FITS file
1059 nelements = naxes[0] * naxes[1];
1060 if (fits_write_img(fptr, TUSHORT, fpixel, nelements, converted.data(), &status))
1061 {
1063 qCWarning(KSTARS_EKOS_GUIDE) << "fits_write_img failed:" << error_status;
1064 status = 0;
1065 fits_close_file(fptr, &status);
1066 free(fits_buffer);
1067 return;
1068 }
1069
1070 if (fits_flush_file(fptr, &status))
1071 {
1073 qCWarning(KSTARS_EKOS_GUIDE) << "fits_flush_file failed:" << error_status;
1074 status = 0;
1075 fits_close_file(fptr, &status);
1076 free(fits_buffer);
1077 return;
1078 }
1079
1080 if (fits_close_file(fptr, &status))
1081 {
1083 qCWarning(KSTARS_EKOS_GUIDE) << "fits_close_file failed:" << error_status;
1084 free(fits_buffer);
1085 return;
1086 }
1087
1088 //This loads the FITS file in the Guide FITSView
1089 //Then it updates the Summary Screen
1091 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(fits_buffer), fits_buffer_size);
1092 fdata.reset(new FITSData(), &QObject::deleteLater);
1093 fdata->loadFromBuffer(buffer, "fits");
1094 free(fits_buffer);
1095 m_GuideFrame->loadData(fdata);
1096
1097 m_GuideFrame->updateFrame();
1098 m_GuideFrame->setTrackingBox(QRect(0, 0, width, height));
1099 emit newStarPixmap(m_GuideFrame->getTrackingBoxPixmap());
1100}
1101
1102void PHD2::setEquipmentConnected()
1103{
1104 if (connection != EQUIPMENT_CONNECTED)
1105 {
1106 setConnectedRetries = 0;
1107 connection = EQUIPMENT_CONNECTED;
1108 emit newStatus(Ekos::GUIDE_CONNECTED);
1109 updateGuideParameters();
1110 requestExposureDurations();
1111 requestCurrentEquipmentUpdate();
1112 }
1113}
1114
1115void PHD2::updateGuideParameters()
1116{
1117 if (pixelScale == 0)
1118 requestPixelScale();
1119 requestExposureTime();
1120 checkDEGuideMode();
1121}
1122
1123//This section handles the methods/requests sent to PHD2, some are not implemented.
1124
1125//capture_single_frame
1126void PHD2::captureSingleFrame()
1127{
1128 sendPHD2Request("capture_single_frame");
1129}
1130
1131//clear_calibration
1132bool PHD2::clearCalibration()
1133{
1134 if (connection != EQUIPMENT_CONNECTED)
1135 {
1136 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1137 emit newStatus(Ekos::GUIDE_ABORTED);
1138 return false;
1139 }
1140
1142 //This instructs PHD2 which calibration to clear.
1143 args << "mount";
1144 sendPHD2Request("clear_calibration", args);
1145
1146 return true;
1147}
1148
1149//dither
1150bool PHD2::dither(double pixels)
1151{
1152 if (connection != EQUIPMENT_CONNECTED)
1153 {
1154 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1155 emit newStatus(Ekos::GUIDE_ABORTED);
1156 return false;
1157 }
1158
1159 if (isSettling)
1160 {
1161 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring dither requested while already settling";
1162
1163 if (!isDitherActive)
1164 {
1165 // act like we just dithered so we get the appropriate
1166 // effects after the settling completes
1167 handlePHD2AppState(DITHERING);
1168 isDitherActive = true;
1169 }
1170 return true;
1171 }
1172
1174 QJsonObject settle;
1175
1176 int ditherTimeout = static_cast<int>(Options::ditherTimeout());
1177
1178 settle.insert("pixels", static_cast<double>(Options::ditherThreshold()));
1179 settle.insert("time", static_cast<int>(Options::ditherSettle()));
1180 settle.insert("timeout", ditherTimeout);
1181
1182 // Pixels
1183 args << pixels;
1184 // RA Only?
1185 args << false;
1186 // Settle
1187 args << settle;
1188
1189 isSettling = true;
1190 isDitherActive = true;
1191
1192 // PHD2 will send a SettleDone event shortly after the settling
1193 // timeout in PHD2. We don't really need a timer here, but we'll
1194 // set one anyway (belt and suspenders). Make sure to give an
1195 // extra time allowance since PHD2 won't report its timeout until
1196 // the completion of the next guide exposure after the timeout
1197 // period expires.
1198 enum { TIMEOUT_EXTRA_SECONDS = 60 }; // at least as long as any reasonable guide exposure
1200 ditherTimer->start(millis);
1201
1202 sendPHD2Request("dither", args);
1203
1204 handlePHD2AppState(DITHERING);
1205
1206 return true;
1207}
1208
1209//find_star
1210//flip_calibration
1211//get_algo_param_names
1212//get_algo_param
1213
1214//get_app_state
1215void PHD2::requestAppState()
1216{
1217 sendPHD2Request("get_app_state");
1218}
1219
1220//get_calibrated
1221//get_calibration_data
1222
1223//get_connected
1224void PHD2::checkIfEquipmentConnected()
1225{
1226 sendPHD2Request("get_connected");
1227}
1228
1229//get_cooler_status
1230//get_current_equipment
1231void PHD2::requestCurrentEquipmentUpdate()
1232{
1233 sendPHD2Request("get_current_equipment");
1234}
1235
1236//get_dec_guide_mode
1237void PHD2::checkDEGuideMode()
1238{
1239 sendPHD2Request("get_dec_guide_mode");
1240}
1241
1242//get_exposure
1243void PHD2::requestExposureTime()
1244{
1245 sendPHD2Request("get_exposure");
1246}
1247
1248//get_exposure_durations
1249void PHD2::requestExposureDurations()
1250{
1251 sendPHD2Request("get_exposure_durations");
1252}
1253
1254//get_lock_position
1255void PHD2::requestLockPosition()
1256{
1257 sendPHD2Request("get_lock_position");
1258}
1259//get_lock_shift_enabled
1260//get_lock_shift_params
1261//get_paused
1262
1263//get_pixel_scale
1264void PHD2::requestPixelScale()
1265{
1266 sendPHD2Request("get_pixel_scale");
1267}
1268
1269//get_profile
1270//get_profiles
1271//get_search_region
1272//get_sensor_temperature
1273
1274//get_star_image
1275void PHD2::requestStarImage(int size)
1276{
1277 if (starImageRequested)
1278 {
1279 if (Options::verboseLogging())
1280 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: skip extra star image request";
1281 return;
1282 }
1283
1285 args2 << size; // This is both the width and height.
1286 sendPHD2Request("get_star_image", args2);
1287
1288 starImageRequested = true;
1289}
1290
1291//get_use_subframes
1292
1293//guide
1294bool PHD2::guide()
1295{
1296 if (state == GUIDING)
1297 {
1298 emit newLog(i18n("PHD2: Guiding is already running."));
1299 emit newStatus(Ekos::GUIDE_GUIDING);
1300 return true;
1301 }
1302
1303 if (connection != EQUIPMENT_CONNECTED)
1304 {
1305 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1306 emit newStatus(Ekos::GUIDE_ABORTED);
1307 return false;
1308 }
1309
1311 QJsonObject settle;
1312
1313 settle.insert("pixels", static_cast<double>(Options::ditherThreshold()));
1314 settle.insert("time", static_cast<int>(Options::ditherSettle()));
1315 settle.insert("timeout", static_cast<int>(Options::ditherTimeout()));
1316
1317 // Settle param
1318 args << settle;
1319 // Recalibrate param
1320 args << false;
1321
1322 errorLog.clear();
1323
1324 isSettling = true;
1325 sendPHD2Request("guide", args);
1326
1327 return true;
1328}
1329
1330//guide_pulse
1331//loop
1332void PHD2::loop()
1333{
1334 sendPHD2Request("loop");
1335}
1336//save_image
1337//set_algo_param
1338
1339//set_connected
1340void PHD2::connectEquipment(bool enable)
1341{
1342 if (connection == EQUIPMENT_CONNECTED && enable == true)
1343 return;
1344
1345 if (connection == EQUIPMENT_DISCONNECTED && enable == false)
1346 return;
1347
1348 if (setConnectedRetries++ > MAX_SET_CONNECTED_RETRIES)
1349 {
1350 setConnectedRetries = 0;
1351 connection = EQUIPMENT_DISCONNECTED;
1352 emit newStatus(Ekos::GUIDE_DISCONNECTED);
1353 return;
1354 }
1355
1356 pixelScale = 0 ;
1357
1359
1360 // connected = enable
1361 args << enable;
1362
1363 if (enable)
1364 emit newLog(i18n("PHD2: Connecting Equipment. . ."));
1365 else
1366 emit newLog(i18n("PHD2: Disconnecting Equipment. . ."));
1367
1368 sendPHD2Request("set_connected", args);
1369}
1370
1371//set_dec_guide_mode
1372void PHD2::requestSetDEGuideMode(bool deEnabled, bool nEnabled,
1373 bool sEnabled) //Possible Settings Off, Auto, North, and South
1374{
1376
1377 if(deEnabled)
1378 {
1379 if(nEnabled && sEnabled)
1380 args << "Auto";
1381 else if(nEnabled)
1382 args << "North";
1383 else if(sEnabled)
1384 args << "South";
1385 else
1386 args << "Off";
1387 }
1388 else
1389 {
1390 args << "Off";
1391 }
1392
1393 sendPHD2Request("set_dec_guide_mode", args);
1394}
1395
1396//set_exposure
1397void PHD2::requestSetExposureTime(int time) //Note: time is in milliseconds
1398{
1400 args << time;
1401 sendPHD2Request("set_exposure", args);
1402}
1403
1404//set_lock_position
1405void PHD2::setLockPosition(double x, double y)
1406{
1407 // Note: false will mean if a guide star is near the coordinates, it will use that.
1409 args << x << y << false;
1410 sendPHD2Request("set_lock_position", args);
1411}
1412//set_lock_shift_enabled
1413//set_lock_shift_params
1414
1415//set_paused
1416bool PHD2::suspend()
1417{
1418 if (connection != EQUIPMENT_CONNECTED)
1419 {
1420 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1421 emit newStatus(Ekos::GUIDE_ABORTED);
1422 return false;
1423 }
1424
1426
1427 // Paused param
1428 args << true;
1429 // FULL param
1430 args << "full";
1431
1432 sendPHD2Request("set_paused", args);
1433
1434 if (abortTimer->isActive())
1435 {
1436 // Avoid that the a preceding lost star event leads to an abort while guiding is suspended.
1437 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout cancelled.";
1438 abortTimer->stop();
1439 }
1440
1441 return true;
1442}
1443
1444//set_paused (also)
1445bool PHD2::resume()
1446{
1447 if (connection != EQUIPMENT_CONNECTED)
1448 {
1449 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1450 emit newStatus(Ekos::GUIDE_ABORTED);
1451 return false;
1452 }
1453
1455
1456 // Paused param
1457 args << false;
1458
1459 sendPHD2Request("set_paused", args);
1460
1461 if (state == LOSTLOCK)
1462 {
1463 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: Lost star timeout restarted.";
1464 abortTimer->start(static_cast<int>(Options::guideLostStarTimeout()) * 1000);
1465 }
1466
1467 return true;
1468}
1469
1470//set_profile
1471//shutdown
1472
1473//stop_capture
1474bool PHD2::abort()
1475{
1476 if (connection != EQUIPMENT_CONNECTED)
1477 {
1478 emit newLog(i18n("PHD2 Error: Equipment not connected."));
1479 emit newStatus(Ekos::GUIDE_ABORTED);
1480 return false;
1481 }
1482
1483 abortTimer->stop();
1484
1485 sendPHD2Request("stop_capture");
1486 return true;
1487}
1488
1489//This method is not handled by PHD2
1490bool PHD2::calibrate()
1491{
1492 // We don't explicitly do calibration since it is done in the guide step by PHD2 anyway
1493 //emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
1494 return true;
1495}
1496
1497//This is how information requests and commands for PHD2 are handled
1498
1499void PHD2::sendRpcCall(QJsonObject &call, PHD2ResultType resultType)
1500{
1501 assert(resultType != NO_RESULT); // should be a real request
1502 assert(pendingRpcResultType == NO_RESULT); // only one pending RPC call at a time
1503
1504 if (tcpSocket->state() == QTcpSocket::ConnectedState)
1505 {
1506 int rpcId = nextRpcId++;
1507 call.insert("id", rpcId);
1508
1510
1511 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: request:" << request;
1512
1513 request.append("\r\n");
1514
1515 qint64 const n = tcpSocket->write(request);
1516
1517 if ((int) n == request.size())
1518 {
1519 // RPC call succeeded, remember ID and expected result type
1520 pendingRpcId = rpcId;
1521 pendingRpcResultType = resultType;
1522 }
1523 else
1524 {
1525 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: unexpected short write:" << n << "bytes of" << request.size();
1526 }
1527 }
1528}
1529
1530void PHD2::sendNextRpcCall()
1531{
1532 if (pendingRpcResultType != NO_RESULT)
1533 return; // a request is currently outstanding
1534
1535 if (rpcRequestQueue.empty())
1536 return; // no queued requests
1537
1538 RpcCall &call = rpcRequestQueue.front();
1539 sendRpcCall(call.call, call.resultType);
1540 rpcRequestQueue.pop_front();
1541}
1542
1543void PHD2::sendPHD2Request(const QString &method, const QJsonArray &args)
1544{
1545 assert(methodResults.contains(method));
1546
1547 PHD2ResultType resultType = methodResults[method];
1548
1550
1551 jsonRPC.insert("jsonrpc", "2.0");
1552 jsonRPC.insert("method", method);
1553
1554 if (!args.empty())
1555 jsonRPC.insert("params", args);
1556
1557 if (pendingRpcResultType == NO_RESULT)
1558 {
1559 // no outstanding rpc call, send it right off
1560 sendRpcCall(jsonRPC, resultType);
1561 }
1562 else
1563 {
1564 // there is already an outstanding call, enqueue this call
1565 // until the prior call completes
1566
1567 if (Options::verboseLogging())
1568 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: defer call" << method;
1569
1570 rpcRequestQueue.push_back(RpcCall(jsonRPC, resultType));
1571 }
1572}
1573
1574PHD2::PHD2ResultType PHD2::takeRequestFromList(const QJsonObject &response)
1575{
1576 if (Q_UNLIKELY(!response.contains("id")))
1577 {
1578 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with no id";
1579 return NO_RESULT;
1580 }
1581
1582 int id = response["id"].toInt();
1583
1584 if (Q_UNLIKELY(id != pendingRpcId))
1585 {
1586 // RPC id mismatch -- this should never happen, something is
1587 // seriously wrong
1588 qCDebug(KSTARS_EKOS_GUIDE) << "PHD2: ignoring unexpected response with id" << id;
1589 return NO_RESULT;
1590 }
1591
1592 PHD2ResultType val = pendingRpcResultType;
1593 pendingRpcResultType = NO_RESULT;
1594 return val;
1595}
1596
1597}
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
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
Int toInt() const const
bool contains(const Key &key) const const
T value(const Key &key) const const
void readyRead()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QByteArray toJson(JsonFormat format) const const
iterator insert(QLatin1StringView key, const QJsonValue &value)
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 Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.