Kstars

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

KDE's Doxygen guidelines are available online.