Kstars

message.cpp
1 /* Ekos Live Message
2 
3  SPDX-FileCopyrightText: 2018 Jasem Mutlaq <[email protected]>
4 
5  Message Channel
6 
7  SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "message.h"
11 #include "commands.h"
12 #include "profileinfo.h"
13 #include "indi/drivermanager.h"
14 #include "auxiliary/ksmessagebox.h"
15 #include "kstars.h"
16 #include "kstarsdata.h"
17 #include "ekos_debug.h"
18 #include "fitsviewer/fitsview.h"
19 #include "ksalmanac.h"
20 #include "skymapcomposite.h"
21 #include "catalogobject.h"
22 #include "ekos/auxiliary/darklibrary.h"
23 #include "skymap.h"
24 #include "Options.h"
25 
26 #include <KActionCollection>
27 #include <basedevice.h>
28 #include <QUuid>
29 
30 namespace EkosLive
31 {
32 Message::Message(Ekos::Manager *manager): m_Manager(manager), m_DSOManager(CatalogsDB::dso_db_path())
33 {
34  connect(&m_WebSocket, &QWebSocket::connected, this, &Message::onConnected);
35  connect(&m_WebSocket, &QWebSocket::disconnected, this, &Message::onDisconnected);
36  connect(&m_WebSocket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this,
37  &Message::onError);
38 
39  connect(manager, &Ekos::Manager::newModule, this, &Message::sendModuleState);
40 
41  m_ThrottleTS = QDateTime::currentDateTime();
42 }
43 
44 void Message::connectServer()
45 {
46  QUrl requestURL(m_URL);
47 
49  query.addQueryItem("username", m_AuthResponse["username"].toString());
50  query.addQueryItem("token", m_AuthResponse["token"].toString());
51  if (m_AuthResponse.contains("remoteToken"))
52  query.addQueryItem("remoteToken", m_AuthResponse["remoteToken"].toString());
53  query.addQueryItem("email", m_AuthResponse["email"].toString());
54  query.addQueryItem("from_date", m_AuthResponse["from_date"].toString());
55  query.addQueryItem("to_date", m_AuthResponse["to_date"].toString());
56  query.addQueryItem("plan_id", m_AuthResponse["plan_id"].toString());
57  query.addQueryItem("type", m_AuthResponse["type"].toString());
58 
59  requestURL.setPath("/message/ekos");
60  requestURL.setQuery(query);
61 
62 
63  m_WebSocket.open(requestURL);
64 
65  qCInfo(KSTARS_EKOS) << "Connecting to Websocket server at" << requestURL.toDisplayString();
66 }
67 
68 void Message::disconnectServer()
69 {
70  m_WebSocket.close();
71 }
72 
73 void Message::onConnected()
74 {
75  qCInfo(KSTARS_EKOS) << "Connected to Message Websocket server at" << m_URL.toDisplayString();
76 
77  m_isConnected = true;
78  m_ReconnectTries = 0;
79 
80  connect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Message::onTextReceived, Qt::UniqueConnection);
81 
82  sendConnection();
83  sendProfiles();
84 
85  emit connected();
86 }
87 
88 void Message::onDisconnected()
89 {
90  qCInfo(KSTARS_EKOS) << "Disconnected from Message Websocket server.";
91  m_isConnected = false;
92  disconnect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Message::onTextReceived);
93  m_PropertyCache.clear();
94 
95  emit disconnected();
96 }
97 
98 void Message::onError(QAbstractSocket::SocketError error)
99 {
100  qCritical(KSTARS_EKOS) << "Websocket connection error" << m_WebSocket.errorString();
103  {
104  if (m_ReconnectTries++ < RECONNECT_MAX_TRIES)
105  QTimer::singleShot(RECONNECT_INTERVAL, this, &Message::connectServer);
106  }
107 }
108 
109 
110 void Message::onTextReceived(const QString &message)
111 {
112  qCInfo(KSTARS_EKOS) << "Websocket Message" << message;
114  auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error);
115  if (error.error != QJsonParseError::NoError)
116  {
117  qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
118  return;
119  }
120 
121  // TODO add check to verify token!
122  // const QString serverToken = serverMessage.object().value("token").toString();
123 
124  // if (serverToken != token)
125  // {
126  // qCWarning(KSTARS_EKOS) << "Invalid token received from server!" << serverToken;
127  // return;
128  // }
129 
130  const QJsonObject msgObj = serverMessage.object();
131  const QString command = msgObj["type"].toString();
132  const QJsonObject payload = msgObj["payload"].toObject();
133 
134  if (command == commands[GET_CONNECTION])
135  {
136  sendConnection();
137  }
138  else if (command == commands[LOGOUT])
139  {
140  emit expired();
141  return;
142  }
143  else if (command == commands[SET_CLIENT_STATE])
144  {
145  // If client is connected, make sure clock is ticking
146  if (payload["state"].toBool(false))
147  {
148  qCInfo(KSTARS_EKOS) << "EkosLive client is connected.";
149 
150  // If the clock is PAUSED, run it now and sync time as well.
151  if (KStarsData::Instance()->clock()->isActive() == false)
152  {
153  qCInfo(KSTARS_EKOS) << "Resuming and syncing clock.";
154  KStarsData::Instance()->clock()->start();
155  QAction *a = KStars::Instance()->actionCollection()->action("time_to_now");
156  if (a)
157  a->trigger();
158  }
159  }
160  // Otherwise, if KStars was started in PAUSED state
161  // then we pause here as well to save power.
162  else
163  {
164  qCInfo(KSTARS_EKOS) << "EkosLive client is disconnected.";
165  // It was started with paused state, so let's pause IF Ekos is not running
166  if (KStars::Instance()->isStartedWithClockRunning() == false && m_Manager->ekosStatus() == Ekos::CommunicationStatus::Idle)
167  {
168  qCInfo(KSTARS_EKOS) << "Stopping the clock.";
169  KStarsData::Instance()->clock()->stop();
170  }
171  }
172  }
173  else if (command == commands[GET_DRIVERS])
174  sendDrivers();
175  else if (command == commands[GET_PROFILES])
176  sendProfiles();
177  else if (command == commands[GET_SCOPES])
178  sendScopes();
179  else if (command.startsWith("scope_"))
180  processScopeCommands(command, payload);
181  else if (command.startsWith("profile_"))
182  processProfileCommands(command, payload);
183  else if (command.startsWith("astro_"))
184  processAstronomyCommands(command, payload);
185  else if (command == commands[DIALOG_GET_RESPONSE])
186  processDialogResponse(payload);
187  else if (command.startsWith("option_"))
188  processOptionsCommands(command, payload);
189  else if (command.startsWith("scheduler"))
190  processSchedulerCommands(command, payload);
191 
192  if (m_Manager->getEkosStartingStatus() != Ekos::Success)
193  return;
194 
195  if (command == commands[GET_STATES])
196  sendStates();
197  else if (command == commands[GET_CAMERAS])
198  {
199  sendCameras();
200  // Try to trigger any signals based on current camera list
201  if (m_Manager->captureModule())
202  m_Manager->captureModule()->checkCamera();
203  }
204  else if (command == commands[GET_MOUNTS])
205  sendMounts();
206  else if (command == commands[GET_FILTER_WHEELS])
207  sendFilterWheels();
208  else if (command == commands[GET_DOMES])
209  sendDomes();
210  else if (command == commands[GET_CAPS])
211  sendCaps();
212  else if (command == commands[GET_STELLARSOLVER_PROFILES])
213  sendStellarSolverProfiles();
214  else if (command == commands[GET_DEVICES])
215  sendDevices();
216  else if (command.startsWith("capture_"))
217  processCaptureCommands(command, payload);
218  else if (command.startsWith("mount_"))
219  processMountCommands(command, payload);
220  else if (command.startsWith("focus_"))
221  processFocusCommands(command, payload);
222  else if (command.startsWith("guide_"))
223  processGuideCommands(command, payload);
224  else if (command.startsWith("align_"))
225  processAlignCommands(command, payload);
226  else if (command.startsWith("polar_"))
227  processPolarCommands(command, payload);
228  else if (command.startsWith("dslr_"))
229  processDSLRCommands(command, payload);
230  else if (command.startsWith("fm_"))
231  processFilterManagerCommands(command, payload);
232  else if (command.startsWith("dark_library_"))
233  processDarkLibraryCommands(command, payload);
234  else if (command.startsWith("device_"))
235  processDeviceCommands(command, payload);
236 
237 }
238 
239 void Message::sendCameras()
240 {
241  if (m_isConnected == false)
242  return;
243 
244  QJsonArray cameraList;
245 
246  for(auto &oneDevice : m_Manager->getAllDevices())
247  {
248  auto camera = dynamic_cast<ISD::Camera*>(oneDevice->getConcreteDevice(INDI::BaseDevice::CCD_INTERFACE));
249  if (!camera)
250  continue;
251 
252  auto primaryChip = camera->getChip(ISD::CameraChip::PRIMARY_CCD);
253 
254  double temperature = Ekos::INVALID_VALUE;
255  camera->getTemperature(&temperature);
256 
257  QJsonObject oneCamera =
258  {
259  {"name", camera->getDeviceName()},
260  {"canBin", primaryChip->canBin()},
261  {"hasTemperature", camera->hasCooler()},
262  {"canCool", camera->canCool()},
263  {"isoList", QJsonArray::fromStringList(primaryChip->getISOList())},
264  {"hasVideo", camera->hasVideoStream()},
265  {"hasGain", camera->hasGain()}
266  };
267 
268  cameraList.append(oneCamera);
269  }
270 
271  sendResponse(commands[GET_CAMERAS], cameraList);
272 
273  // Send initial state as well.
274  for(auto &oneDevice : m_Manager->getAllDevices())
275  {
276  auto camera = dynamic_cast<ISD::Camera*>(oneDevice->getConcreteDevice(INDI::BaseDevice::CCD_INTERFACE));
277  if (!camera)
278  continue;
279 
280  QJsonObject state = {{"name", camera->getDeviceName()}};
281  double value = 0;
282 
283  if (camera->canCool())
284  {
285  camera->getTemperature(&value);
286  state["temperature"] = value;
287  }
288  if (camera->hasGain())
289  {
290  camera->getGain(&value);
291  state["gain"] = value;
292  }
293  if (camera->getChip(ISD::CameraChip::PRIMARY_CCD)->getISOIndex() >= 0)
294  {
295  state["iso"] = camera->getChip(ISD::CameraChip::PRIMARY_CCD)->getISOIndex();
296  }
297 
298  sendResponse(commands[NEW_CAMERA_STATE], state);
299  }
300 
301  if (m_Manager->captureModule())
302  sendCaptureSettings(m_Manager->captureModule()->getPresetSettings());
303  if (m_Manager->alignModule())
304  sendAlignSettings(m_Manager->alignModule()->getSettings());
305  if (m_Manager->focusModule())
306  {
307  sendResponse(commands[FOCUS_SET_SETTINGS], m_Manager->focusModule()->getSettings());
308  sendResponse(commands[FOCUS_SET_PRIMARY_SETTINGS], m_Manager->focusModule()->getPrimarySettings());
309  sendResponse(commands[FOCUS_SET_PROCESS_SETTINGS], m_Manager->focusModule()->getProcessSettings());
310  sendResponse(commands[FOCUS_SET_MECHANICS_SETTINGS], m_Manager->focusModule()->getMechanicsSettings());
311  }
312  if (m_Manager->guideModule())
313  sendGuideSettings(m_Manager->guideModule()->getSettings());
314 }
315 
316 void Message::sendMounts()
317 {
318  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
319  return;
320 
321  QJsonArray mountList;
322 
323  for(auto &oneDevice : m_Manager->getAllDevices())
324  {
325  auto mount = dynamic_cast<ISD::Mount*>(oneDevice->getConcreteDevice(INDI::BaseDevice::TELESCOPE_INTERFACE));
326  if (!mount)
327  continue;
328 
329  QJsonObject oneMount =
330  {
331  {"name", mount->getDeviceName()},
332  {"canPark", mount->canPark()},
333  {"canSync", mount->canSync()},
334  {"canControlTrack", mount->canControlTrack()},
335  {"hasSlewRates", mount->hasSlewRates()},
336  {"slewRates", QJsonArray::fromStringList(mount->slewRates())},
337  };
338 
339  mountList.append(oneMount);
340  }
341 
342  sendResponse(commands[GET_MOUNTS], mountList);
343 
344  // Also send initial slew rate
345  for(auto &oneDevice : m_Manager->getAllDevices())
346  {
347  auto mount = dynamic_cast<ISD::Mount*>(oneDevice->getConcreteDevice(INDI::BaseDevice::TELESCOPE_INTERFACE));
348  if (!mount)
349  continue;
350 
351  QJsonObject slewRate =
352  {
353  {"name", mount->getDeviceName() },
354  {"slewRate", mount->getSlewRate() },
355  {"pierSide", mount->pierSide() },
356  };
357 
358  sendResponse(commands[NEW_MOUNT_STATE], slewRate);
359  }
360 
361  if (m_Manager->mountModule())
362  {
363  // Mount module states
364  QJsonObject mountModuleSettings =
365  {
366  {"altitudeLimitsEnabled", m_Manager->mountModule()->altitudeLimitsEnabled()},
367  {"altitudeLimitsMin", m_Manager->mountModule()->altitudeLimits()[0]},
368  {"altitudeLimitsMax", m_Manager->mountModule()->altitudeLimits()[1]},
369  {"haLimitEnabled", m_Manager->mountModule()->hourAngleLimitEnabled()},
370  {"haLimitValue", m_Manager->mountModule()->hourAngleLimit()},
371  {"meridianFlipEnabled", m_Manager->mountModule()->meridianFlipEnabled()},
372  {"meridianFlipValue", m_Manager->mountModule()->meridianFlipValue()},
373  {"autoParkEnabled", m_Manager->mountModule()->autoParkEnabled()},
374  };
375 
376  sendResponse(commands[MOUNT_GET_SETTINGS], mountModuleSettings);
377  }
378 }
379 
380 void Message::sendDomes()
381 {
382  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
383  return;
384 
385  QJsonArray domeList;
386 
387  for(auto &oneDevice : m_Manager->getAllDevices())
388  {
389  auto dome = dynamic_cast<ISD::Dome*>(oneDevice->getConcreteDevice(INDI::BaseDevice::DOME_INTERFACE));
390  if (!dome)
391  continue;
392 
393  QJsonObject oneDome =
394  {
395  {"name", dome->getDeviceName()},
396  {"canPark", dome->canPark()},
397  {"canGoto", dome->canAbsMove()},
398  {"canAbort", dome->canAbort()},
399  };
400 
401  domeList.append(oneDome);
402  }
403 
404  sendResponse(commands[GET_DOMES], domeList);
405 
406  // Also send initial azimuth
407  for(auto &oneDevice : m_Manager->getAllDevices())
408  {
409  auto dome = dynamic_cast<ISD::Dome*>(oneDevice->getConcreteDevice(INDI::BaseDevice::DOME_INTERFACE));
410  if (!dome)
411  continue;
412 
414  {
415  { "name", dome->getDeviceName()},
416  { "status", ISD::Dome::getStatusString(dome->status())}
417  };
418 
419  if (dome->canAbsMove())
420  status["az"] = dome->azimuthPosition();
421 
422  sendResponse(commands[NEW_DOME_STATE], status);
423 
424  }
425 }
426 
427 void Message::sendCaps()
428 {
429  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
430  return;
431 
432  QJsonArray capList;
433 
434  for(auto &oneDevice : m_Manager->getAllDevices())
435  {
436  auto dustcap = dynamic_cast<ISD::DustCap*>(oneDevice->getConcreteDevice(INDI::BaseDevice::DUSTCAP_INTERFACE));
437  if (!dustcap)
438  continue;
439 
440  QJsonObject oneCap =
441  {
442  {"name", dustcap->getDeviceName()},
443  {"canPark", dustcap->canPark()},
444  {"hasLight", dustcap->hasLight()},
445  };
446 
447  capList.append(oneCap);
448  }
449 
450  sendResponse(commands[GET_CAPS], capList);
451 
452  for(auto &oneDevice : m_Manager->getAllDevices())
453  {
454  auto dustcap = dynamic_cast<ISD::DustCap*>(oneDevice->getConcreteDevice(INDI::BaseDevice::DUSTCAP_INTERFACE));
455  if (!dustcap)
456  continue;
457 
459  {
460  { "name", dustcap->getDeviceName()},
461  { "status", ISD::DustCap::getStatusString(dustcap->status())},
462  { "lightS", dustcap->isLightOn()}
463  };
464 
465  updateCapStatus(status);
466  }
467 }
468 
469 
470 void Message::sendStellarSolverProfiles()
471 {
472  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
473  return;
474 
475  QJsonObject profiles;
476 
477  if (m_Manager->focusModule())
478  profiles.insert("focus", QJsonArray::fromStringList(m_Manager->focusModule()->getStellarSolverProfiles()));
479  // TODO
480  // if (m_Manager->guideModule())
481  // profiles.insert("guide", QJsonArray::fromStringList(m_Manager->guideModule()->getStellarSolverProfiles()));
482  if (m_Manager->alignModule())
483  profiles.insert("align", QJsonArray::fromStringList(m_Manager->alignModule()->getStellarSolverProfiles()));
484 
485 
486  sendResponse(commands[GET_STELLARSOLVER_PROFILES], profiles);
487 }
488 
489 void Message::sendDrivers()
490 {
491  if (m_isConnected == false)
492  return;
493 
494  sendResponse(commands[GET_DRIVERS], DriverManager::Instance()->getDriverList());
495 }
496 
497 void Message::sendDevices()
498 {
499  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
500  return;
501 
502  QJsonArray deviceList;
503 
504  for(auto &gd : m_Manager->getAllDevices())
505  {
506  QJsonObject oneDevice =
507  {
508  {"name", gd->getDeviceName()},
509  {"connected", gd->isConnected()},
510  {"version", gd->getDriverVersion()},
511  {"interface", static_cast<int>(gd->getDriverInterface())},
512  };
513 
514  deviceList.append(oneDevice);
515  }
516 
517  sendResponse(commands[GET_DEVICES], deviceList);
518 }
519 
520 void Message::sendScopes()
521 {
522  if (m_isConnected == false)
523  return;
524 
525  QJsonArray scopeList;
526 
527  QList<OAL::Scope *> allScopes;
528  KStarsData::Instance()->userdb()->GetAllScopes(allScopes);
529 
530  for (auto &scope : allScopes)
531  scopeList.append(scope->toJson());
532 
533  sendResponse(commands[GET_SCOPES], scopeList);
534 }
535 
536 void Message::sendTemperature(double value)
537 {
538  ISD::Camera *oneCCD = dynamic_cast<ISD::Camera*>(sender());
539 
540  if (oneCCD)
541  {
542  QJsonObject temperature =
543  {
544  {"name", oneCCD->getDeviceName()},
545  {"temperature", value}
546  };
547 
548  sendResponse(commands[NEW_CAMERA_STATE], temperature);
549  }
550 }
551 
552 void Message::sendFilterWheels()
553 {
554  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
555  return;
556 
557  QJsonArray filterList;
558 
559  for(auto &oneDevice : m_Manager->getAllDevices())
560  {
561  auto filterwheel = dynamic_cast<ISD::FilterWheel*>(oneDevice->getConcreteDevice(INDI::BaseDevice::FILTER_INTERFACE));
562  if (!filterwheel)
563  continue;
564 
565  auto prop = filterwheel->getProperty("FILTER_NAME");
566  if (!prop)
567  break;
568 
569  auto filterNames = prop->getText();
570  if (!filterNames)
571  break;
572 
573  QJsonArray filters;
574  for (const auto &it : *filterNames)
575  filters.append(it.getText());
576 
577  QJsonObject oneFilter =
578  {
579  {"name", filterwheel->getDeviceName()},
580  {"filters", filters}
581  };
582 
583  filterList.append(oneFilter);
584  }
585 
586  sendResponse(commands[GET_FILTER_WHEELS], filterList);
587 }
588 
589 void Message::setCapturePresetSettings(const QJsonObject &settings)
590 {
591  m_Manager->captureModule()->setPresetSettings(settings);
592 }
593 
594 void Message::processCaptureCommands(const QString &command, const QJsonObject &payload)
595 {
596  Ekos::Capture *capture = m_Manager->captureModule();
597 
598  if (capture == nullptr)
599  {
600  qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as capture module is not available";
601  return;
602  }
603 
604  if (command == commands[CAPTURE_PREVIEW])
605  {
606  setCapturePresetSettings(payload);
607  capture->captureOne();
608  }
609  else if (command == commands[CAPTURE_TOGGLE_CAMERA])
610  {
611  capture->setCamera(payload["camera"].toString());
612  sendCaptureSettings(capture->getPresetSettings());
613  }
614  else if (command == commands[CAPTURE_TOGGLE_FILTER_WHEEL])
615  {
616  capture->setFilterWheel(payload["fw"].toString());
617  sendCaptureSettings(capture->getPresetSettings());
618  }
619  else if (command == commands[CAPTURE_TOGGLE_VIDEO])
620  {
621  capture->setVideoLimits(payload["maxBufferSize"].toInt(512), payload["maxPreviewFPS"].toInt(10));
622  capture->toggleVideo(payload["enabled"].toBool());
623  }
624  else if (command == commands[CAPTURE_START])
625  capture->start();
626  else if (command == commands[CAPTURE_STOP])
627  capture->stop();
628  else if (command == commands[CAPTURE_LOOP])
629  {
630  setCapturePresetSettings(payload);
631  capture->startFraming();
632  }
633  else if (command == commands[CAPTURE_GET_SEQUENCES])
634  {
635  sendCaptureSequence(capture->getSequence());
636  }
637  else if (command == commands[CAPTURE_ADD_SEQUENCE])
638  {
639  // Set capture settings first
640  setCapturePresetSettings(payload["preset"].toObject());
641 
642  // Then sequence settings
643  capture->setCount(static_cast<uint16_t>(payload["count"].toInt()));
644  capture->setDelay(static_cast<uint16_t>(payload["delay"].toInt()));
645 
646  // File Settings
647  m_Manager->captureModule()->setFileSettings(payload["file"].toObject());
648 
649  // Calibration Settings
650  m_Manager->captureModule()->setCalibrationSettings(payload["calibration"].toObject());
651 
652  // Now add job
653  capture->addJob();
654  }
655  else if (command == commands[CAPTURE_REMOVE_SEQUENCE])
656  {
657  if (capture->removeJob(payload["index"].toInt()) == false)
658  sendCaptureSequence(capture->getSequence());
659  }
660  else if (command == commands[CAPTURE_CLEAR_SEQUENCES])
661  {
662  capture->clearSequenceQueue();
663  }
664  else if (command == commands[CAPTURE_SET_LIMITS])
665  {
666  capture->setLimitSettings(payload);
667  }
668  else if (command == commands[CAPTURE_GET_LIMITS])
669  {
670  sendResponse(commands[CAPTURE_GET_LIMITS], capture->getLimitSettings());
671  }
672  else if (command == commands[CAPTURE_SAVE_SEQUENCE_FILE])
673  {
674  capture->saveSequenceQueue(payload["filepath"].toString());
675  }
676  else if (command == commands[CAPTURE_LOAD_SEQUENCE_FILE])
677  {
678  capture->loadSequenceQueue(payload["filepath"].toString());
679  }
680  else if (command == commands[CAPTURE_GET_CALIBRATION_SETTINGS])
681  {
682  sendResponse(commands[CAPTURE_GET_CALIBRATION_SETTINGS], capture->getCalibrationSettings());
683  }
684  else if (command == commands[CAPTURE_GET_FILE_SETTINGS])
685  {
686  sendResponse(commands[CAPTURE_GET_FILE_SETTINGS], capture->getFileSettings());
687  }
688  else if (command == commands[CAPTURE_GENERATE_DARK_FLATS])
689  {
690  capture->generateDarkFlats();
691  }
692 }
693 
694 void Message::sendCaptureSequence(const QJsonArray &sequenceArray)
695 {
696  sendResponse(commands[CAPTURE_GET_SEQUENCES], sequenceArray);
697 }
698 
699 void Message::sendCaptureSettings(const QJsonObject &settings)
700 {
701  sendResponse(commands[CAPTURE_SET_SETTINGS], settings);
702 }
703 
704 void Message::sendAlignSettings(const QJsonObject &settings)
705 {
706  sendResponse(commands[ALIGN_SET_SETTINGS], settings);
707 }
708 
709 void Message::sendGuideSettings(const QJsonObject &settings)
710 {
711  sendResponse(commands[GUIDE_SET_SETTINGS], settings);
712 }
713 
714 void Message::sendFocusSettings(const QJsonObject &settings)
715 {
716  sendResponse(commands[FOCUS_SET_SETTINGS], settings);
717 }
718 
719 void Message::sendSchedulerSettings(const QJsonObject &settings)
720 {
721  sendResponse(commands[SCHEDULER_GET_SETTINGS], settings);
722 }
723 
724 void Message::processGuideCommands(const QString &command, const QJsonObject &payload)
725 {
726  Ekos::Guide *guide = m_Manager->guideModule();
727 
728  if (guide == nullptr)
729  {
730  qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as guide module is not available";
731  return;
732  }
733 
734  if (command == commands[GUIDE_START])
735  {
736  guide->guide();
737  }
738  else if (command == commands[GUIDE_CAPTURE])
739  guide->capture();
740  else if (command == commands[GUIDE_LOOP])
741  guide->loop();
742  else if (command == commands[GUIDE_STOP])
743  guide->abort();
744  else if (command == commands[GUIDE_CLEAR])
745  guide->clearCalibration();
746  else if (command == commands[GUIDE_SET_SETTINGS])
747  {
748  guide->setSettings(payload);
749  sendGuideSettings(m_Manager->guideModule()->getSettings());
750  }
751 }
752 
753 void Message::processFocusCommands(const QString &command, const QJsonObject &payload)
754 {
755  Ekos::Focus *focus = m_Manager->focusModule();
756 
757  if (focus == nullptr)
758  {
759  qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as focus module is not available";
760  return;
761  }
762 
763  if (command == commands[FOCUS_START])
764  focus->start();
765  else if (command == commands[FOCUS_CAPTURE])
766  {
767  focus->resetFrame();
768  focus->capture();
769  }
770  else if (command == commands[FOCUS_STOP])
771  focus->abort();
772  else if (command == commands[FOCUS_RESET])
773  focus->resetFrame();
774  else if (command == commands[FOCUS_IN])
775  focus->focusIn(payload["steps"].toInt());
776  else if (command == commands[FOCUS_OUT])
777  focus->focusOut(payload["steps"].toInt());
778  else if (command == commands[FOCUS_LOOP])
779  focus->startFraming();
780  else if (command == commands[FOCUS_SET_SETTINGS])
781  focus->setSettings(payload);
782  else if (command == commands[FOCUS_SET_CROSSHAIR])
783  {
784  double x = payload["x"].toDouble();
785  double y = payload["y"].toDouble();
786  focus->selectFocusStarFraction(x, y);
787  }
788  else if (command == commands[FOCUS_SET_PRIMARY_SETTINGS])
789  focus->setPrimarySettings(payload);
790  else if (command == commands[FOCUS_SET_PROCESS_SETTINGS])
791  focus->setProcessSettings(payload);
792  else if (command == commands[FOCUS_SET_MECHANICS_SETTINGS])
793  focus->setMechanicsSettings(payload);
794  else if (command == commands[FOCUS_GET_PRIMARY_SETTINGS])
795  sendResponse(commands[FOCUS_GET_PRIMARY_SETTINGS], focus->getPrimarySettings());
796  else if (command == commands[FOCUS_GET_PROCESS_SETTINGS])
797  sendResponse(commands[FOCUS_GET_PROCESS_SETTINGS], focus->getProcessSettings());
798  else if (command == commands[FOCUS_GET_MECHANICS_SETTINGS])
799  sendResponse(commands[FOCUS_GET_MECHANICS_SETTINGS], focus->getMechanicsSettings());
800 }
801 
802 void Message::processMountCommands(const QString &command, const QJsonObject &payload)
803 {
804  Ekos::Mount *mount = m_Manager->mountModule();
805 
806  if (mount == nullptr)
807  {
808  qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as mount module is not available";
809  return;
810  }
811 
812  if (command == commands[MOUNT_ABORT])
813  mount->abort();
814  else if (command == commands[MOUNT_PARK])
815  mount->park();
816  else if (command == commands[MOUNT_UNPARK])
817  mount->unpark();
818  else if (command == commands[MOUNT_SET_TRACKING])
819  mount->setTrackEnabled(payload["enabled"].toBool());
820  else if (command == commands[MOUNT_SYNC_RADE])
821  {
822  mount->setJ2000Enabled(payload["isJ2000"].toBool());
823  mount->sync(payload["ra"].toString(), payload["de"].toString());
824  }
825  else if (command == commands[MOUNT_SYNC_TARGET])
826  {
827  mount->syncTarget(payload["target"].toString());
828  }
829  else if (command == commands[MOUNT_GOTO_RADE])
830  {
831  mount->setJ2000Enabled(payload["isJ2000"].toBool());
832  mount->slew(payload["ra"].toString(), payload["de"].toString());
833  }
834  else if (command == commands[MOUNT_GOTO_TARGET])
835  {
836  mount->gotoTarget(payload["target"].toString());
837  }
838  else if (command == commands[MOUNT_SET_SLEW_RATE])
839  {
840  int rate = payload["rate"].toInt(-1);
841  if (rate >= 0)
842  mount->setSlewRate(rate);
843  }
844  else if (command == commands[MOUNT_SET_MOTION])
845  {
846  QString direction = payload["direction"].toString();
847  ISD::Mount::MotionCommand action = payload["action"].toBool(false) ?
848  ISD::Mount::MOTION_START : ISD::Mount::MOTION_STOP;
849 
850  if (direction == "N")
851  mount->motionCommand(action, ISD::Mount::MOTION_NORTH, -1);
852  else if (direction == "S")
853  mount->motionCommand(action, ISD::Mount::MOTION_SOUTH, -1);
854  else if (direction == "E")
855  mount->motionCommand(action, -1, ISD::Mount::MOTION_EAST);
856  else if (direction == "W")
857  mount->motionCommand(action, -1, ISD::Mount::MOTION_WEST);
858  }
859  else if (command == commands[MOUNT_SET_ALTITUDE_LIMITS])
860  {
861  QList<double> limits;
862  limits.append(payload["min"].toDouble(0));
863  limits.append(payload["max"].toDouble(90));
864  mount->setAltitudeLimits(limits);
865  mount->setAltitudeLimitsEnabled(payload["enabled"].toBool());
866  }
867  else if (command == commands[MOUNT_SET_HA_LIMIT])
868  {
869  mount->setHourAngleLimit(payload["value"].toDouble());
870  mount->setHourAngleLimitEnabled(payload["enabled"].toBool());
871  }
872  else if (command == commands[MOUNT_SET_MERIDIAN_FLIP])
873  {
874  // Meridian flip value is in degress. Need to convert to hours.
875  mount->setMeridianFlipValues(payload["enabled"].toBool(),
876  payload["value"].toDouble() / 15.0);
877  }
878  else if (command == commands[MOUNT_SET_EVERYDAY_AUTO_PARK])
879  {
880  const bool enabled = payload["enabled"].toBool();
881  mount->setAutoParkDailyEnabled(enabled);
882  }
883  else if (command == commands[MOUNT_SET_AUTO_PARK])
884  {
885  const bool enabled = payload["enabled"].toBool();
886  // Only set startup time when enabled.
887  if (enabled)
888  mount->setAutoParkStartup(QTime::fromString(payload["value"].toString()));
889  mount->setAutoParkEnabled(enabled);
890  }
891  else if (command == commands[MOUNT_GOTO_PIXEL])
892  {
893  const auto name = payload["camera"].toString();
894  const auto xFactor = payload["x"].toDouble();
895  const auto yFactor = payload["y"].toDouble();
896 
897  for(auto &oneDevice : m_Manager->getAllDevices())
898  {
899  auto camera = dynamic_cast<ISD::Camera*>(oneDevice->getConcreteDevice(INDI::BaseDevice::CCD_INTERFACE));
900  if (!camera || camera->getDeviceName() != name)
901  continue;
902 
903  auto primaryChip = camera->getChip(ISD::CameraChip::PRIMARY_CCD);
904 
905  if (!primaryChip)
906  break;
907 
908  auto imageData = primaryChip->getImageData();
909  if (!imageData || imageData->hasWCS() == false)
910  break;
911 
912  auto x = xFactor * imageData->width();
913  auto y = yFactor * imageData->height();
914 
915  QPointF point(x, y);
916  SkyPoint coord;
917  if (imageData->pixelToWCS(point, coord))
918  {
919  // J2000 -> JNow
920  coord.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
921  mount->gotoTarget(coord);
922  break;
923  }
924  }
925  }
926 }
927 
928 void Message::processAlignCommands(const QString &command, const QJsonObject &payload)
929 {
930  Ekos::Align *align = m_Manager->alignModule();
931 
932  if (align == nullptr)
933  {
934  qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as align module is not available";
935  return;
936  }
937 
938  if (command == commands[ALIGN_SOLVE])
939  {
940  align->updateTargetCoords();
941  align->captureAndSolve();
942  }
943  else if (command == commands[ALIGN_SET_SETTINGS])
944  align->setSettings(payload);
945  else if (command == commands[ALIGN_STOP])
946  align->abort();
947  else if (command == commands[ALIGN_LOAD_AND_SLEW])
948  {
949  QString filename = QDir::tempPath() + QDir::separator() +
950  QString("XXXXXXloadslew.%1").arg(payload["ext"].toString("fits"));
951  QTemporaryFile file(filename);
952  file.setAutoRemove(false);
953  file.open();
954  file.write(QByteArray::fromBase64(payload["data"].toString().toLatin1()));
955  file.close();
956  align->loadAndSlew(file.fileName());
957  }
958  else if (command == commands[ALIGN_MANUAL_ROTATOR_TOGGLE])
959  {
960  align->toggleManualRotator(payload["toggled"].toBool());
961  }
962 }
963 
964 void Message::setAlignStatus(Ekos::AlignState newState)
965 {
966  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
967  return;
968 
969  QJsonObject alignState =
970  {
971  {"status", Ekos::alignStates[newState]}
972  };
973 
974  sendResponse(commands[NEW_ALIGN_STATE], alignState);
975 }
976 
977 void Message::setAlignSolution(const QVariantMap &solution)
978 {
979  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
980  return;
981 
982  QJsonObject alignState =
983  {
984  {"solution", QJsonObject::fromVariantMap(solution)},
985  };
986 
987  sendResponse(commands[NEW_ALIGN_STATE], alignState);
988 }
989 
990 void Message::processSchedulerCommands(const QString &command, const QJsonObject &payload)
991 {
992  Ekos::Scheduler *scheduler = m_Manager->schedulerModule();
993 
994  if (command == commands[SCHEDULER_SET_PRIMARY_SETTINGS])
995  {
996  scheduler->setPrimarySettings(payload);
997  }
998  else if (command == commands[SCHEDULER_SET_JOB_STARTUP_CONDITIONS])
999  {
1000  scheduler->setJobStartupConditions(payload);
1001  sendSchedulerSettings(m_Manager->schedulerModule()->getSchedulerSettings());
1002  }
1003  else if (command == commands[SCHEDULER_SET_JOB_CONSTRAINTS])
1004  {
1005  scheduler->setJobConstraints(payload);
1006  sendSchedulerSettings(m_Manager->schedulerModule()->getSchedulerSettings());
1007  }
1008  else if (command == commands[SCHEDULER_SET_JOB_COMPLETION_SETTINGS])
1009  {
1010  scheduler->setJobCompletionConditions(payload);
1011  sendSchedulerSettings(m_Manager->schedulerModule()->getSchedulerSettings());
1012  }
1013  else if (command == commands[SCHEDULER_SET_OBSERVATORY_STARTUP_PROCEDURE])
1014  {
1015  scheduler->setObservatoryStartupProcedure(payload);
1016  sendSchedulerSettings(m_Manager->schedulerModule()->getSchedulerSettings());
1017  }
1018  else if (command == commands[SCHEDULER_SET_ABORTED_JOB_MANAGEMENT])
1019  {
1020  scheduler->setAbortedJobManagementSettings(payload);
1021  sendSchedulerSettings(m_Manager->schedulerModule()->getSchedulerSettings());
1022  }
1023  else if (command == commands[SCHEDULER_SET_OBSERVATORY_SHUTDOWN_PROCEDURE])
1024  {
1025  scheduler->setObservatoryShutdownProcedure(payload);
1026  sendSchedulerSettings(m_Manager->schedulerModule()->getSchedulerSettings());
1027  }
1028  else if (command == commands[SCHEDULER_GET_JOBS])
1029  {
1030  sendSchedulerJobs();
1031  }
1032  else if (command == commands[SCHEDULER_ADD_JOBS])
1033  {
1034  scheduler->addJob();
1035  }
1036  else if(command == commands[SCHEDULER_REMOVE_JOBS])
1037  {
1038  int index = payload["index"].toInt();
1039  scheduler->removeOneJob(index);
1040  }
1041  else if(command == commands[SCHEDULER_GET_SETTINGS])
1042  {
1043  sendResponse(commands[SCHEDULER_GET_SETTINGS], scheduler->getSchedulerSettings());
1044  }
1045  else if(command == commands[SCHEDULER_START_JOB])
1046  {
1047  scheduler->toggleScheduler();
1048  }
1049 
1050 }
1051 
1052 void Message::processPolarCommands(const QString &command, const QJsonObject &payload)
1053 {
1054  Ekos::Align *align = m_Manager->alignModule();
1055  Ekos::PolarAlignmentAssistant *paa = align->polarAlignmentAssistant();
1056 
1057  if (!paa)
1058  return;
1059 
1060  if (command == commands[PAH_START])
1061  {
1062  paa->startPAHProcess();
1063  }
1064  if (command == commands[PAH_STOP])
1065  {
1066  paa->stopPAHProcess();
1067  }
1068  if (command == commands[PAH_SET_SETTINGS])
1069  {
1070  paa->setPAHSettings(payload);
1071  }
1072  else if (command == commands[PAH_REFRESH])
1073  {
1074  paa->setPAHRefreshDuration(payload["value"].toDouble(1));
1075  paa->startPAHRefreshProcess();
1076  }
1077  else if (command == commands[PAH_SET_ALGORITHM])
1078  {
1079  auto algorithmCombo = paa->findChild<QComboBox*>("PAHRefreshAlgorithmCombo");
1080  if (algorithmCombo)
1081  algorithmCombo->setCurrentIndex(static_cast<Ekos::PolarAlignmentAssistant::PAHRefreshAlgorithm>(payload["value"].toInt(1)));
1082  }
1083  else if (command == commands[PAH_RESET_VIEW])
1084  {
1085  emit resetPolarView();
1086  }
1087  else if (command == commands[PAH_SET_CROSSHAIR])
1088  {
1089  double x = payload["x"].toDouble();
1090  double y = payload["y"].toDouble();
1091 
1092  if (m_BoundingRect.isNull() == false)
1093  {
1094  // #1 Find actual dimension inside the bounding rectangle
1095  // since if we have bounding rectable then x,y fractions are INSIDE it
1096  double boundX = x * m_BoundingRect.width();
1097  double boundY = y * m_BoundingRect.height();
1098 
1099  // #2 Find fraction of the dimensions above the full image size
1100  // Add to it the bounding rect top left offsets
1101  // factors in the change caused by zoom
1102  x = ((boundX + m_BoundingRect.x()) / (m_CurrentZoom / 100)) / m_ViewSize.width();
1103  y = ((boundY + m_BoundingRect.y()) / (m_CurrentZoom / 100)) / m_ViewSize.height();
1104 
1105  }
1106 
1107  paa->setPAHCorrectionOffsetPercentage(x, y);
1108  }
1109  else if (command == commands[PAH_SELECT_STAR_DONE])
1110  {
1111  // This button was removed from the desktop PAA scheme.
1112  // Nothing to do.
1113  // TODO: Make sure this works.
1114  }
1115  else if (command == commands[PAH_REFRESHING_DONE])
1116  {
1117  paa->stopPAHProcess();
1118  }
1119  else if (command == commands[PAH_SLEW_DONE])
1120  {
1121  paa->setPAHSlewDone();
1122  }
1123  else if (command == commands[PAH_PAH_SET_ZOOM])
1124  {
1125  double scale = payload["scale"].toDouble();
1126  align->setAlignZoom(scale);
1127  }
1128 
1129 }
1130 
1131 void Message::setPAHStage(Ekos::PolarAlignmentAssistant::PAHStage stage)
1132 {
1133  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1134  return;
1135 
1136  Q_UNUSED(stage)
1137  Ekos::Align *align = m_Manager->alignModule();
1138 
1139  Ekos::PolarAlignmentAssistant *paa = align->polarAlignmentAssistant();
1140 
1141  if (!paa)
1142  return;
1143 
1144  QJsonObject polarState =
1145  {
1146  {"stage", paa->getPAHStageString(false)}
1147  };
1148 
1149 
1150  // Increase size when select star
1151  if (stage == Ekos::PolarAlignmentAssistant::PAH_STAR_SELECT)
1152  align->zoomAlignView();
1153 
1154  sendResponse(commands[NEW_POLAR_STATE], polarState);
1155 }
1156 
1157 void Message::setPAHMessage(const QString &message)
1158 {
1159  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1160  return;
1161 
1162  QTextDocument doc;
1163  doc.setHtml(message);
1164  QJsonObject polarState =
1165  {
1166  {"message", doc.toPlainText()}
1167  };
1168 
1169  sendResponse(commands[NEW_POLAR_STATE], polarState);
1170 }
1171 
1172 void Message::setPolarResults(QLineF correctionVector, double polarError, double azError, double altError)
1173 {
1174  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1175  return;
1176 
1177  this->correctionVector = correctionVector;
1178 
1179  QPointF center = 0.5 * correctionVector.p1() + 0.5 * correctionVector.p2();
1180  QJsonObject vector =
1181  {
1182  {"center_x", center.x()},
1183  {"center_y", center.y()},
1184  {"mag", correctionVector.length()},
1185  {"pa", correctionVector.angle()},
1186  {"error", polarError},
1187  {"azError", azError},
1188  {"altError", altError}
1189  };
1190 
1191  QJsonObject polarState =
1192  {
1193  {"vector", vector}
1194  };
1195 
1196  sendResponse(commands[NEW_POLAR_STATE], polarState);
1197 }
1198 
1199 void Message::setUpdatedErrors(double total, double az, double alt)
1200 {
1201  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1202  return;
1203 
1204  QJsonObject error =
1205  {
1206  {"updatedError", total},
1207  {"updatedAZError", az},
1208  {"updatedALTError", alt}
1209  };
1210 
1211  sendResponse(commands[NEW_POLAR_STATE], error);
1212 }
1213 
1214 void Message::setPAHEnabled(bool enabled)
1215 {
1216  if (m_isConnected == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1217  return;
1218 
1219  QJsonObject polarState =
1220  {
1221  {"enabled", enabled}
1222  };
1223 
1224  sendResponse(commands[NEW_POLAR_STATE], polarState);
1225 }
1226 
1227 void Message::processProfileCommands(const QString &command, const QJsonObject &payload)
1228 {
1229  if (command == commands[START_PROFILE])
1230  {
1231  if (m_Manager->getEkosStartingStatus() != Ekos::Idle)
1232  m_Manager->stop();
1233 
1234  m_Manager->setProfile(payload["name"].toString());
1235  // Always Sync time before we start
1236  KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc());
1237  m_Manager->start();
1238  }
1239  else if (command == commands[STOP_PROFILE])
1240  {
1241  m_Manager->stop();
1242 
1243  // Close all FITS Viewers
1244  KStars::Instance()->clearAllViewers();
1245 
1246  m_PropertySubscriptions.clear();
1247  m_PropertyCache.clear();
1248  }
1249  else if (command == commands[ADD_PROFILE])
1250  {
1251  m_Manager->addNamedProfile(payload);
1252  sendProfiles();
1253  }
1254  else if (command == commands[UPDATE_PROFILE])
1255  {
1256  m_Manager->editNamedProfile(payload);
1257  sendProfiles();
1258  }
1259  else if (command == commands[GET_PROFILE])
1260  {
1261  m_Manager->getNamedProfile(payload["name"].toString());
1262  }
1263  else if (command == commands[DELETE_PROFILE])
1264  {
1265  m_Manager->deleteNamedProfile(payload["name"].toString());
1266  sendProfiles();
1267  }
1268  else if (command == commands[SET_PROFILE_MAPPING])
1269  {
1270  m_Manager->setProfileMapping(payload);
1271  }
1272  else if (command == commands[SET_PROFILE_PORT_SELECTION])
1273  {
1274  requestPortSelection(false);
1275  m_Manager->acceptPortSelection();
1276  }
1277 }
1278 
1279 void Message::sendProfiles()
1280 {
1281  QJsonArray profileArray;
1282 
1283  for (const auto &oneProfile : m_Manager->profiles)
1284  profileArray.append(oneProfile->toJson());
1285 
1286  QJsonObject profiles =
1287  {
1288  {"selectedProfile", m_Manager->getCurrentProfile()->name},
1289  {"profiles", profileArray}
1290  };
1291  sendResponse(commands[GET_PROFILES], profiles);
1292 }
1293 
1294 void Message::sendSchedulerJobs()
1295 {
1296  QJsonObject jobs =
1297  {
1298  {"jobs", m_Manager->schedulerModule()->getJSONJobs()}
1299  };
1300  sendResponse(commands[SCHEDULER_GET_JOBS], jobs);
1301 }
1302 
1303 void Message::sendSchedulerJobList(QJsonArray jobsList)
1304 {
1305  QJsonObject jobs =
1306  {
1307  {"jobs", jobsList}
1308  };
1309  sendResponse(commands[SCHEDULER_GET_JOBS], jobs);
1310 }
1311 
1312 void Message::setEkosStatingStatus(Ekos::CommunicationStatus status)
1313 {
1314  if (status == Ekos::Pending)
1315  return;
1316 
1317  QJsonObject connectionState =
1318  {
1319  {"connected", true},
1320  {"online", status == Ekos::Success}
1321  };
1322  sendResponse(commands[NEW_CONNECTION_STATE], connectionState);
1323 }
1324 
1325 void Message::processOptionsCommands(const QString &command, const QJsonObject &payload)
1326 {
1327  if (command == commands[OPTION_SET])
1328  {
1329  const QJsonArray options = payload["options"].toArray();
1330  for (const auto &oneOption : options)
1331  Options::self()->setProperty(oneOption["name"].toString().toLatin1(), oneOption["value"].toVariant());
1332  return;
1333  }
1334  else if (command == commands[OPTION_GET])
1335  {
1336  const QJsonArray options = payload["options"].toArray();
1337  QJsonArray result;
1338  for (const auto &oneOption : options)
1339  {
1340  const auto name = oneOption["name"].toString();
1341  QVariant value = Options::self()->property(name.toLatin1());
1342  QVariantMap map;
1343  map["name"] = name;
1344  map["value"] = value;
1345  result.append(QJsonObject::fromVariantMap(map));
1346  }
1347  sendResponse(commands[OPTION_GET], result);
1348  return;
1349  }
1350  if (command == commands[OPTION_SET_HIGH_BANDWIDTH])
1351  m_Options[OPTION_SET_HIGH_BANDWIDTH] = payload["value"].toBool(true);
1352  else if (command == commands[OPTION_SET_IMAGE_TRANSFER])
1353  m_Options[OPTION_SET_IMAGE_TRANSFER] = payload["value"].toBool(true);
1354  else if (command == commands[OPTION_SET_NOTIFICATIONS])
1355  m_Options[OPTION_SET_NOTIFICATIONS] = payload["value"].toBool(true);
1356  else if (command == commands[OPTION_SET_CLOUD_STORAGE])
1357  m_Options[OPTION_SET_CLOUD_STORAGE] = payload["value"].toBool(false);
1358 
1359  emit optionsChanged(m_Options);
1360 }
1361 
1362 void Message::processScopeCommands(const QString &command, const QJsonObject &payload)
1363 {
1364  if (command == commands[ADD_SCOPE])
1365  {
1366  KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(),
1367  payload["driver"].toString(),
1368  payload["type"].toString(), payload["focal_length"].toDouble(), payload["aperture"].toDouble());
1369  }
1370  else if (command == commands[UPDATE_SCOPE])
1371  {
1372  KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(),
1373  payload["driver"].toString(),
1374  payload["type"].toString(), payload["focal_length"].toDouble(), payload["aperture"].toDouble(), payload["id"].toString());
1375  }
1376  else if (command == commands[DELETE_SCOPE])
1377  {
1378  KStarsData::Instance()->userdb()->DeleteEquipment("telescope", payload["id"].toInt());
1379  }
1380 
1381  sendScopes();
1382 }
1383 
1384 void Message::processDSLRCommands(const QString &command, const QJsonObject &payload)
1385 {
1386  if (command == commands[DSLR_SET_INFO])
1387  {
1388  if (m_Manager->captureModule())
1389  m_Manager->captureModule()->addDSLRInfo(
1390  payload["model"].toString(),
1391  payload["width"].toInt(),
1392  payload["height"].toInt(),
1393  payload["pixelw"].toDouble(),
1394  payload["pixelh"].toDouble());
1395 
1396  }
1397 }
1398 
1399 void Message::processFilterManagerCommands(const QString &command, const QJsonObject &payload)
1400 {
1401  if (command == commands[FM_GET_DATA])
1402  {
1403  QJsonObject data = m_Manager->getFilterManager()->toJSON();
1404  sendResponse(commands[FM_GET_DATA], data);
1405  }
1406  else if (command == commands[FM_SET_DATA])
1407  {
1408  m_Manager->getFilterManager()->setFilterData(payload);
1409  }
1410 }
1411 
1412 void Message::processDarkLibraryCommands(const QString &command, const QJsonObject &payload)
1413 {
1414  if (command == commands[DARK_LIBRARY_START])
1415  {
1416  Ekos::DarkLibrary::Instance()->setDarkSettings(payload);
1417  Ekos::DarkLibrary::Instance()->start();
1418  }
1419  else if(command == commands[DARK_LIBRARY_SET_SETTINGS])
1420  {
1421  Ekos::DarkLibrary::Instance()->setDarkSettings(payload);
1422  }
1423  else if(command == commands[DARK_LIBRARY_SET_CAMERA_PRESETS])
1424  {
1425  Ekos::DarkLibrary::Instance()->setCameraPresets(payload);
1426  }
1427  else if (command == commands[DARK_LIBRARY_STOP])
1428  {
1429  Ekos::DarkLibrary::Instance()->stop();
1430  }
1431  else if (command == commands[DARK_LIBRARY_GET_MASTERS_IMAGE])
1432  {
1433  const int row = payload["row"].toInt();
1434  Ekos::DarkLibrary::Instance()->loadIndexInView(row);
1435  }
1436  else if (command == commands[DARK_LIBRARY_GET_DARK_SETTINGS])
1437  {
1438  sendResponse(commands[DARK_LIBRARY_GET_DARK_SETTINGS], Ekos::DarkLibrary::Instance()->getDarkSettings());
1439  }
1440  else if (command == commands[DARK_LIBRARY_GET_CAMERA_PRESETS])
1441  {
1442  sendResponse(commands[DARK_LIBRARY_GET_CAMERA_PRESETS], Ekos::DarkLibrary::Instance()->getCameraPresets());
1443  }
1444  else if (command == commands[DARK_LIBRARY_SET_DEFECT_PIXELS])
1445  {
1446  Ekos::DarkLibrary::Instance()->setDefectPixels(payload);
1447  }
1448  else if (command == commands[DARK_LIBRARY_SAVE_MAP])
1449  {
1450  Ekos::DarkLibrary::Instance()->saveMapB->click();
1451  }
1452  else if (command == commands[DARK_LIBRARY_SET_DEFECT_FRAME])
1453  {
1454  Ekos::DarkLibrary::Instance()->setDefectMapEnabled(false);
1455  }
1456  else if (command == commands[DARK_LIBRARY_SET_DEFECT_SETTINGS])
1457  {
1458  Ekos::DarkLibrary::Instance()->setDefectSettings(payload);
1459  }
1460  else if (command == commands[DARK_LIBRARY_GET_DEFECT_SETTINGS])
1461  {
1462  sendResponse(commands[DARK_LIBRARY_GET_DEFECT_SETTINGS], Ekos::DarkLibrary::Instance()->getDefectSettings());
1463  }
1464  else if (command == commands[DARK_LIBRARY_GET_VIEW_MASTERS])
1465  {
1466  sendResponse(commands[DARK_LIBRARY_GET_VIEW_MASTERS], Ekos::DarkLibrary::Instance()->getViewMasters());
1467  }
1468  else if (command == commands[DARK_LIBRARY_CLEAR_MASTERS_ROW])
1469  {
1470  const int rowIndex = payload["row"].toInt();
1471  Ekos::DarkLibrary::Instance()->clearRow(rowIndex);
1472  }
1473 }
1474 
1475 
1476 void Message::processDeviceCommands(const QString &command, const QJsonObject &payload)
1477 {
1478  QList<ISD::GenericDevice *> devices = m_Manager->getAllDevices();
1479  QString device = payload["device"].toString();
1480 
1481  // In case we want to UNSUBSCRIBE from all at once
1482  if (device.isEmpty() && command == commands[DEVICE_PROPERTY_UNSUBSCRIBE])
1483  {
1484  m_PropertySubscriptions.clear();
1485  m_PropertyCache.clear();
1486  return;
1487  }
1488 
1489  // For the device
1490  auto pos = std::find_if(devices.begin(), devices.end(), [device](ISD::GenericDevice * oneDevice)
1491  {
1492  return (oneDevice->getDeviceName() == device);
1493  });
1494 
1495  if (pos == devices.end())
1496  return;
1497  auto oneDevice = *pos;
1498 
1499  // Get specific property
1500  if (command == commands[DEVICE_PROPERTY_GET])
1501  {
1502  QJsonObject propObject;
1503  if (oneDevice->getJSONProperty(payload["property"].toString(), propObject, payload["compact"].toBool(true)))
1504  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_GET]}, {"payload", propObject}}).toJson(
1506  }
1507  // Set specific property
1508  else if (command == commands[DEVICE_PROPERTY_SET])
1509  {
1510  oneDevice->setJSONProperty(payload["property"].toString(), payload["elements"].toArray());
1511  }
1512  // Return ALL properties
1513  else if (command == commands[DEVICE_GET])
1514  {
1516  for (const auto &oneProp : *oneDevice->getProperties())
1517  {
1518  QJsonObject singleProp;
1519  if (oneDevice->getJSONProperty(oneProp->getName(), singleProp, payload["compact"].toBool(false)))
1520  properties.append(singleProp);
1521  }
1522 
1523  QJsonObject response =
1524  {
1525  {"device", device},
1526  {"properties", properties}
1527  };
1528 
1529  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_GET]}, {"payload", response}}).toJson(
1531  }
1532  // Subscribe to one or more properties
1533  // When subscribed, the updates are immediately pushed as soon as they are received.
1534  else if (command == commands[DEVICE_PROPERTY_SUBSCRIBE])
1535  {
1536  const QJsonArray properties = payload["properties"].toArray();
1537  const QJsonArray groups = payload["groups"].toArray();
1538 
1539  // Get existing subscribed props for this device
1540  QSet<QString> props;
1541  if (m_PropertySubscriptions.contains(device))
1542  props = m_PropertySubscriptions[device];
1543 
1544  // If it is just a single property, let's insert it to props.
1545  if (properties.isEmpty() == false)
1546  {
1547  for (const auto &oneProp : properties)
1548  props.insert(oneProp.toString());
1549  }
1550  // If group is specified, then we need to add ALL properties belonging to this group.
1551  else if (groups.isEmpty() == false)
1552  {
1553  QVariantList indiGroups = groups.toVariantList();
1554  for (auto &oneProp : *oneDevice->getProperties())
1555  {
1556  if (indiGroups.contains(oneProp->getGroupName()))
1557  props.insert(oneProp->getName());
1558  }
1559  }
1560  // Otherwise, subscribe to ALL property in this device
1561  else
1562  {
1563  for (auto &oneProp : *oneDevice->getProperties())
1564  props.insert(oneProp->getName());
1565  }
1566 
1567  m_PropertySubscriptions[device] = props;
1568  }
1569  else if (command == commands[DEVICE_PROPERTY_UNSUBSCRIBE])
1570  {
1571  const QJsonArray properties = payload["properties"].toArray();
1572  const QJsonArray groups = payload["groups"].toArray();
1573 
1574  // Get existing subscribed props for this device
1575  QSet<QString> props;
1576  if (m_PropertySubscriptions.contains(device))
1577  props = m_PropertySubscriptions[device];
1578 
1579  // If it is just a single property, let's insert it to props.
1580  // If it is just a single property, let's insert it to props.
1581  if (properties.isEmpty() == false)
1582  {
1583  for (const auto &oneProp : properties)
1584  props.remove(oneProp.toString());
1585  }
1586  // If group is specified, then we need to add ALL properties belonging to this group.
1587  else if (groups.isEmpty() == false)
1588  {
1589  QVariantList indiGroups = groups.toVariantList();
1590  for (auto &oneProp : *oneDevice->getProperties())
1591  {
1592  if (indiGroups.contains(oneProp->getGroupName()))
1593  props.remove(oneProp->getName());
1594  }
1595  }
1596  // Otherwise, subscribe to ALL property in this device
1597  else
1598  {
1599  for (auto &oneProp : *oneDevice->getProperties())
1600  props.remove(oneProp->getName());
1601  }
1602 
1603  m_PropertySubscriptions[device] = props;
1604  }
1605 }
1606 
1607 void Message::processAstronomyCommands(const QString &command, const QJsonObject &payload)
1608 {
1609  if (command == commands[ASTRO_GET_ALMANC])
1610  {
1611  // Today's date
1612  const KStarsDateTime localTime = KStarsData::Instance()->lt();
1613  // Local Midnight
1614  const KStarsDateTime midnight = KStarsDateTime(localTime.date(), QTime(0, 0), Qt::LocalTime);
1615 
1616  KSAlmanac almanac(midnight, KStarsData::Instance()->geo());
1617 
1618  QJsonObject response =
1619  {
1620  {"SunRise", almanac.getSunRise()},
1621  {"SunSet", almanac.getSunSet()},
1622  {"SunMaxAlt", almanac.getSunMaxAlt()},
1623  {"SunMinAlt", almanac.getSunMinAlt()},
1624  {"MoonRise", almanac.getMoonRise()},
1625  {"MoonSet", almanac.getMoonSet()},
1626  {"MoonPhase", almanac.getMoonPhase()},
1627  {"MoonIllum", almanac.getMoonIllum()},
1628  {"Dawn", almanac.getDawnAstronomicalTwilight()},
1629  {"Dusk", almanac.getDuskAstronomicalTwilight()},
1630 
1631  };
1632 
1633  sendResponse(commands[ASTRO_GET_ALMANC], response);
1634  }
1635  // Get a list of object based on criteria
1636  else if (command == commands[ASTRO_SEARCH_OBJECTS])
1637  {
1638  // Set time if required.
1639  if (payload.contains("jd"))
1640  {
1641  KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
1642  KStarsData::Instance()->clock()->setUTC(jd);
1643  }
1644 
1645  // Search Criteria
1646  // Object Type
1647  auto objectType = static_cast<SkyObject::TYPE>(payload["type"].toInt(SkyObject::GALAXY));
1648  // Azimuth restriction
1649  auto objectDirection = static_cast<Direction>(payload["direction"].toInt(All));
1650  // Maximum Object Magnitude
1651  auto objectMaxMagnitude = payload["maxMagnitude"].toDouble(10);
1652  // Minimum Object Altitude
1653  auto objectMinAlt = payload["minAlt"].toDouble(15);
1654  // Minimum Duration that the object must be above the altitude (if any) seconds.
1655  auto objectMinDuration = payload["minDuration"].toInt(3600);
1656  // Minimum FOV in arcmins.
1657  auto objectMinFOV = payload["minFOV"].toDouble(0);
1658  // Data instance
1659  auto *data = KStarsData::Instance();
1660  // Geo Location
1661  auto *geo = KStarsData::Instance()->geo();
1662  // If we are before dawn, we check object altitude restrictions
1663  // Otherwise, all objects are welcome
1664  auto start = KStarsData::Instance()->lt();
1665  auto end = getNextDawn();
1666  if (start > end)
1667  // Add 1 day
1668  end = end.addDays(1);
1669 
1671  CatalogsDB::CatalogObjectList dsoObjects;
1672  bool isDSO = false;
1673 
1674  switch (objectType)
1675  {
1676  // Stars
1677  case SkyObject::STAR:
1678  case SkyObject::CATALOG_STAR:
1679  allObjects.append(data->skyComposite()->objectLists(SkyObject::STAR));
1680  allObjects.append(data->skyComposite()->objectLists(SkyObject::CATALOG_STAR));
1681  break;
1682  // Planets & Moon
1683  case SkyObject::PLANET:
1684  case SkyObject::MOON:
1685  allObjects.append(data->skyComposite()->objectLists(SkyObject::PLANET));
1686  allObjects.append(data->skyComposite()->objectLists(SkyObject::MOON));
1687  break;
1688  // Comets & Asteroids
1689  case SkyObject::COMET:
1690  allObjects.append(data->skyComposite()->objectLists(SkyObject::COMET));
1691  break;
1692  case SkyObject::ASTEROID:
1693  allObjects.append(data->skyComposite()->objectLists(SkyObject::ASTEROID));
1694  break;
1695  // Clusters
1696  case SkyObject::OPEN_CLUSTER:
1697  dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::OPEN_CLUSTER, objectMaxMagnitude));
1698  isDSO = true;
1699  break;
1700  case SkyObject::GLOBULAR_CLUSTER:
1701  dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GLOBULAR_CLUSTER, objectMaxMagnitude));
1702  isDSO = true;
1703  break;
1704  // Nebuale
1705  case SkyObject::GASEOUS_NEBULA:
1706  dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GASEOUS_NEBULA, objectMaxMagnitude));
1707  isDSO = true;
1708  break;
1709  case SkyObject::PLANETARY_NEBULA:
1710  dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::PLANETARY_NEBULA, objectMaxMagnitude));
1711  isDSO = true;
1712  break;
1713  case SkyObject::GALAXY:
1714  dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GALAXY, objectMaxMagnitude));
1715  isDSO = true;
1716  break;
1717  case SkyObject::SUPERNOVA:
1718  {
1719  if (!Options::showSupernovae())
1720  {
1721  Options::setShowSupernovae(true);
1722  data->setFullTimeUpdate();
1724  }
1725  allObjects.append(data->skyComposite()->objectLists(SkyObject::SUPERNOVA));
1726  }
1727  break;
1728  case SkyObject::SATELLITE:
1729  {
1730  if (!Options::showSatellites())
1731  {
1732  Options::setShowSatellites(true);
1733  data->setFullTimeUpdate();
1735  }
1736  allObjects.append(data->skyComposite()->objectLists(SkyObject::SATELLITE));
1737  }
1738  break;
1739  default:
1740  break;
1741  }
1742 
1743  // Sort by magnitude
1744  std::sort(allObjects.begin(), allObjects.end(), [](const auto & a, const auto & b)
1745  {
1746  return a.second->mag() < b.second->mag();
1747  });
1748 
1750 
1751  // Filter direction, if specified.
1752  if (objectDirection != All)
1753  {
1754  QPair<int, int> Quardent1(270, 360), Quardent2(0, 90), Quardent3(90, 180), Quardent4(180, 270);
1755  QPair<int, int> minAZ, maxAZ;
1756  switch (objectDirection)
1757  {
1758  case North:
1759  minAZ = Quardent1;
1760  maxAZ = Quardent2;
1761  break;
1762  case East:
1763  minAZ = Quardent2;
1764  maxAZ = Quardent3;
1765  break;
1766  case South:
1767  minAZ = Quardent3;
1768  maxAZ = Quardent4;
1769  break;
1770  case West:
1771  minAZ = Quardent4;
1772  maxAZ = Quardent1;
1773  break;
1774  default:
1775  break;
1776  }
1777 
1778  if (isDSO)
1779  {
1780  CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1781  while (dsoIterator != dsoObjects.end())
1782  {
1783  // If there a more efficient way to do this?
1784  const double az = (*dsoIterator).recomputeHorizontalCoords(start, geo).az().Degrees();
1785  if (! ((minAZ.first <= az && az <= minAZ.second) || (maxAZ.first <= az && az <= maxAZ.second)))
1786  dsoIterator = dsoObjects.erase(dsoIterator);
1787  else
1788  ++dsoIterator;
1789  }
1790  }
1791  else
1792  {
1793  while (objectIterator.hasNext())
1794  {
1795  const auto az = objectIterator.next().second->recomputeHorizontalCoords(start, geo).az().Degrees();
1796  if (! ((minAZ.first <= az && az <= minAZ.second) || (maxAZ.first <= az && az <= maxAZ.second)))
1797  objectIterator.remove();
1798  }
1799  }
1800  }
1801 
1802  // Maximum Magnitude
1803  if (!isDSO)
1804  {
1805  objectIterator.toFront();
1806  while (objectIterator.hasNext())
1807  {
1808  auto magnitude = objectIterator.next().second->mag();
1809  // Only filter for objects that have valid magnitude, otherwise, they're automatically included.
1810  if (magnitude != NaN::f && magnitude > objectMaxMagnitude)
1811  objectIterator.remove();
1812  }
1813  }
1814 
1815  // Altitude
1816  if (isDSO)
1817  {
1818  CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1819  while (dsoIterator != dsoObjects.end())
1820  {
1821  double duration = 0;
1822  for (KStarsDateTime t = start; t < end; t = t.addSecs(3600.0))
1823  {
1824  dms LST = geo->GSTtoLST(t.gst());
1825  (*dsoIterator).EquatorialToHorizontal(&LST, geo->lat());
1826  if ((*dsoIterator).alt().Degrees() >= objectMinAlt)
1827  duration += 3600;
1828  }
1829 
1830  if (duration < objectMinDuration)
1831  dsoIterator = dsoObjects.erase(dsoIterator);
1832  else
1833  ++dsoIterator;
1834  }
1835  }
1836  else
1837  {
1838  objectIterator.toFront();
1839  while (objectIterator.hasNext())
1840  {
1841  auto oneObject = objectIterator.next().second;
1842  double duration = 0;
1843 
1844  for (KStarsDateTime t = start; t < end; t = t.addSecs(3600.0))
1845  {
1846  auto LST = geo->GSTtoLST(t.gst());
1847  const_cast<SkyObject *>(oneObject)->EquatorialToHorizontal(&LST, geo->lat());
1848  if (oneObject->alt().Degrees() >= objectMinAlt)
1849  duration += 3600;
1850  }
1851 
1852  if (duration < objectMinDuration)
1853  objectIterator.remove();
1854  }
1855  }
1856 
1857  // For DSOs, check minimum required FOV, if any.
1858  if (isDSO && objectMinFOV > 0)
1859  {
1860  CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1861  while (dsoIterator != dsoObjects.end())
1862  {
1863  if ((*dsoIterator).a() < objectMinFOV)
1864  dsoIterator = dsoObjects.erase(dsoIterator);
1865  else
1866  ++dsoIterator;
1867  }
1868  }
1869 
1870  QStringList searchObjects;
1871  for (auto &oneObject : allObjects)
1872  searchObjects.append(oneObject.second->name());
1873  for (auto &oneObject : dsoObjects)
1874  searchObjects.append(oneObject.name());
1875 
1876  searchObjects.removeDuplicates();
1877  QJsonArray response = QJsonArray::fromStringList(searchObjects);
1878 
1879  sendResponse(commands[ASTRO_SEARCH_OBJECTS], response);
1880  }
1881  else if(command == commands[ASTRO_GET_OBJECT_INFO])
1882  {
1883  const auto name = payload["object"].toString();
1884  QJsonObject info;
1885  SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, false);
1886  if(oneObject)
1887  {
1888  info =
1889  {
1890  {"name", name},
1891  {"designations", QJsonArray::fromStringList(oneObject->longname().split(", "))},
1892  {"magnitude", oneObject->mag()},
1893  {"ra0", oneObject->ra0().Hours()},
1894  {"de0", oneObject->dec0().Degrees()},
1895  {"ra", oneObject->ra().Hours()},
1896  {"de", oneObject->dec().Degrees()},
1897  {"object", true}
1898  };
1899  sendResponse(commands[ASTRO_GET_OBJECT_INFO], info);
1900  }
1901  else
1902  {
1903  info =
1904  {
1905  {"name", name},
1906  {"object", false},
1907  };
1908  sendResponse(commands[ASTRO_GET_OBJECT_INFO], info );
1909  }
1910 
1911  }
1912  // Get a list of object based on criteria
1913  else if (command == commands[ASTRO_GET_OBJECTS_INFO])
1914  {
1915  // Set time if required.
1916  if (payload.contains("jd"))
1917  {
1918  KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
1919  KStarsData::Instance()->clock()->setUTC(jd);
1920  }
1921 
1922  // Object Names
1923  QVariantList objectNames = payload["names"].toArray().toVariantList();
1924  QJsonArray objectsArray;
1925 
1926  for (auto &oneName : objectNames)
1927  {
1928  const QString name = oneName.toString();
1929  SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, false);
1930  if (oneObject)
1931  {
1932  QJsonObject info =
1933  {
1934  {"name", name},
1935  {"designations", QJsonArray::fromStringList(oneObject->longname().split(", "))},
1936  {"magnitude", oneObject->mag()},
1937  {"ra0", oneObject->ra0().Hours()},
1938  {"de0", oneObject->dec0().Degrees()},
1939  {"ra", oneObject->ra().Hours()},
1940  {"de", oneObject->dec().Degrees()},
1941  };
1942 
1943  // If DSO, add angular size.
1944  CatalogObject *dsoObject = dynamic_cast<CatalogObject*>(oneObject);
1945  if (dsoObject)
1946  {
1947  info["a"] = dsoObject->a();
1948  info["b"] = dsoObject->b();
1949  info["pa"] = dsoObject->pa();
1950  }
1951 
1952  objectsArray.append(info);
1953  }
1954  }
1955 
1956  sendResponse(commands[ASTRO_GET_OBJECTS_INFO], objectsArray);
1957  }
1958  // Get a object observability alt/az/ha
1959  else if (command == commands[ASTRO_GET_OBJECTS_OBSERVABILITY])
1960  {
1961  // Set time if required.
1962  if (payload.contains("jd"))
1963  {
1964  KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
1965  KStarsData::Instance()->clock()->setUTC(jd);
1966  }
1967 
1968  // Object Names
1969  QVariantList objectNames = payload["names"].toArray().toVariantList();
1970  QJsonArray objectsArray;
1971 
1972  // Data instance
1973  auto *data = KStarsData::Instance();
1974  // Geo Location
1975  auto *geo = KStarsData::Instance()->geo();
1976  // UT
1977  auto ut = data->ut();
1978 
1979  for (auto &oneName : objectNames)
1980  {
1981  const QString name = oneName.toString();
1982  SkyObject *oneObject = data->skyComposite()->findByName(name, false);
1983  if (oneObject)
1984  {
1985  oneObject->EquatorialToHorizontal(data->lst(), geo->lat());
1986  dms ha(data->lst()->Degrees() - oneObject->ra().Degrees());
1987  QJsonObject info =
1988  {
1989  {"name", name},
1990  {"az", oneObject->az().Degrees()},
1991  {"alt", oneObject->alt().Degrees()},
1992  {"ha", ha.Hours()},
1993  };
1994 
1995  objectsArray.append(info);
1996  }
1997  }
1998 
1999  sendResponse(commands[ASTRO_GET_OBJECTS_OBSERVABILITY], objectsArray);
2000  }
2001  else if (command == commands[ASTRO_GET_OBJECTS_RISESET])
2002  {
2003  // Set time if required.
2004  if (payload.contains("jd"))
2005  {
2006  KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
2007  KStarsData::Instance()->clock()->setUTC(jd);
2008  }
2009 
2010  // Object Names
2011  QVariantList objectNames = payload["names"].toArray().toVariantList();
2012  QJsonArray objectsArray;
2013 
2014  // Data instance
2015  auto *data = KStarsData::Instance();
2016  // Geo Location
2017  auto *geo = KStarsData::Instance()->geo();
2018  // UT
2019  QDateTime midnight = QDateTime(data->lt().date(), QTime());
2020  KStarsDateTime ut = geo->LTtoUT(KStarsDateTime(midnight));
2021 
2022  int DayOffset = 0;
2023  if (data->lt().time().hour() > 12)
2024  DayOffset = 1;
2025 
2026  for (auto &oneName : objectNames)
2027  {
2028  const QString name = oneName.toString();
2029  SkyObject *oneObject = data->skyComposite()->findByName(name, false);
2030  if (oneObject)
2031  {
2032  QJsonObject info;
2033  //Prepare time/position variables
2034  //true = use rise time
2035  QTime riseTime = oneObject->riseSetTime(ut, geo, true);
2036 
2037  //If transit time is before rise time, use transit time for tomorrow
2038  QTime transitTime = oneObject->transitTime(ut, geo);
2039  if (transitTime < riseTime)
2040  transitTime = oneObject->transitTime(ut.addDays(1), geo);
2041 
2042  //If set time is before rise time, use set time for tomorrow
2043  //false = use set time
2044  QTime setTime = oneObject->riseSetTime(ut, geo, false);
2045  //false = use set time
2046  if (setTime < riseTime)
2047  setTime = oneObject->riseSetTime(ut.addDays(1), geo, false);
2048 
2049  info["name"] = name;
2050  if (riseTime.isValid())
2051  {
2052  info["rise"] = QString::asprintf("%02d:%02d", riseTime.hour(), riseTime.minute());
2053  info["set"] = QString::asprintf("%02d:%02d", setTime.hour(), setTime.minute());
2054  }
2055  else
2056  {
2057  if (oneObject->alt().Degrees() > 0.0)
2058  {
2059  info["rise"] = "Circumpolar";
2060  info["set"] = "Circumpolar";
2061  }
2062  else
2063  {
2064  info["rise"] = "Never rises";
2065  info["set"] = "Never rises";
2066  }
2067  }
2068 
2069  info["transit"] = QString::asprintf("%02d:%02d", transitTime.hour(), transitTime.minute());
2070 
2071  QJsonArray altitudes;
2072  for (double h = -12.0; h <= 12.0; h += 0.5)
2073  {
2074  double hour = h + (24.0 * DayOffset);
2075  KStarsDateTime offset = ut.addSecs(hour * 3600.0);
2076  CachingDms LST = geo->GSTtoLST(offset.gst());
2077  oneObject->EquatorialToHorizontal(&LST, geo->lat());
2078  altitudes.append(oneObject->alt().Degrees());
2079  }
2080 
2081  info["altitudes"] = altitudes;
2082 
2083  objectsArray.append(info);
2084  }
2085  }
2086 
2087  sendResponse(commands[ASTRO_GET_OBJECTS_RISESET], objectsArray);
2088  }
2089 }
2090 
2091 KStarsDateTime Message::getNextDawn()
2092 {
2093  // Today's date
2094  const KStarsDateTime localTime = KStarsData::Instance()->lt();
2095  // Local Midnight
2096  const KStarsDateTime midnight = KStarsDateTime(localTime.date(), QTime(0, 0), Qt::LocalTime);
2097  // Almanac
2098  KSAlmanac almanac(midnight, KStarsData::Instance()->geo());
2099  // Next Dawn
2100  KStarsDateTime nextDawn = midnight.addSecs(almanac.getDawnAstronomicalTwilight() * 24.0 * 3600.0);
2101  // If dawn is earliar than now, add a day
2102  if (nextDawn < localTime)
2103  nextDawn.addDays(1);
2104 
2105  return nextDawn;
2106 }
2107 
2108 void Message::requestDSLRInfo(const QString &cameraName)
2109 {
2110  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DSLR_GET_INFO]}, {"payload", cameraName}}).toJson(
2112 }
2113 
2114 void Message::requestPortSelection(bool show)
2115 {
2116  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[GET_PROFILE_PORT_SELECTION]}, {"payload", show}}).toJson(
2118 }
2119 
2120 void Message::sendDialog(const QJsonObject &message)
2121 {
2122  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DIALOG_GET_INFO]}, {"payload", message}}).toJson(
2124 }
2125 
2126 void Message::sendResponse(const QString &command, const QJsonObject &payload)
2127 {
2128  m_WebSocket.sendTextMessage(QJsonDocument({{"type", command}, {"payload", payload}}).toJson(QJsonDocument::Compact));
2129 }
2130 
2131 void Message::sendResponse(const QString &command, const QJsonArray &payload)
2132 {
2133  m_WebSocket.sendTextMessage(QJsonDocument({{"type", command}, {"payload", payload}}).toJson(QJsonDocument::Compact));
2134 }
2135 
2136 void Message::updateMountStatus(const QJsonObject &status, bool throttle)
2137 {
2138  if (m_isConnected == false)
2139  return;
2140 
2141  if (throttle)
2142  {
2144  if (m_ThrottleTS.msecsTo(now) >= THROTTLE_INTERVAL)
2145  {
2146  m_ThrottleTS = now;
2147  sendResponse(commands[NEW_MOUNT_STATE], status);
2148  }
2149  }
2150  else
2151  sendResponse(commands[NEW_MOUNT_STATE], status);
2152 }
2153 
2154 void Message::updateCaptureStatus(const QJsonObject &status)
2155 {
2156  if (m_isConnected == false)
2157  return;
2158 
2159  sendResponse(commands[NEW_CAPTURE_STATE], status);
2160 }
2161 
2162 void Message::updateFocusStatus(const QJsonObject &status)
2163 {
2164  if (m_isConnected == false)
2165  return;
2166 
2167  sendResponse(commands[NEW_FOCUS_STATE], status);
2168 }
2169 
2170 void Message::updateGuideStatus(const QJsonObject &status)
2171 {
2172  if (m_isConnected == false)
2173  return;
2174 
2175  sendResponse(commands[NEW_GUIDE_STATE], status);
2176 }
2177 
2178 void Message::updateDomeStatus(const QJsonObject &status)
2179 {
2180  if (m_isConnected == false)
2181  return;
2182 
2183  sendResponse(commands[NEW_DOME_STATE], status);
2184 }
2185 
2186 void Message::updateCapStatus(const QJsonObject &status)
2187 {
2188  if (m_isConnected == false)
2189  return;
2190 
2191  sendResponse(commands[NEW_CAP_STATE], status);
2192 }
2193 
2194 void Message::sendConnection()
2195 {
2196  if (m_isConnected == false)
2197  return;
2198 
2199  QJsonObject connectionState =
2200  {
2201  {"connected", true},
2202  {"online", m_Manager->getEkosStartingStatus() == Ekos::Success}
2203  };
2204  sendResponse(commands[NEW_CONNECTION_STATE], connectionState);
2205 }
2206 
2207 void Message::sendStates()
2208 {
2209  if (m_isConnected == false)
2210  return;
2211 
2212  // Send capture sequence if one exists
2213  if (m_Manager->captureModule())
2214  {
2215  QJsonObject captureState = {{ "status", getCaptureStatusString(m_Manager->captureModule()->status(), false)}};
2216  sendResponse(commands[NEW_CAPTURE_STATE], captureState);
2217  sendCaptureSequence(m_Manager->captureModule()->getSequence());
2218  }
2219 
2220  if (m_Manager->mountModule())
2221  {
2222  QJsonObject mountState =
2223  {
2224  {"status", m_Manager->mountModule()->statusString(false)},
2225  {"target", m_Manager->mountTarget->text()},
2226  {"slewRate", m_Manager->mountModule()->slewRate()},
2227  {"pierSide", m_Manager->mountModule()->pierSide()}
2228  };
2229 
2230  sendResponse(commands[NEW_MOUNT_STATE], mountState);
2231  }
2232 
2233  if (m_Manager->focusModule())
2234  {
2235  QJsonObject focusState = {{ "status", getFocusStatusString(m_Manager->focusModule()->status(), false)}};
2236  sendResponse(commands[NEW_FOCUS_STATE], focusState);
2237  }
2238 
2239  if (m_Manager->guideModule())
2240  {
2241  QJsonObject guideState = {{ "status", getGuideStatusString(m_Manager->guideModule()->status(), false)}};
2242  sendResponse(commands[NEW_GUIDE_STATE], guideState);
2243  }
2244 
2245  if (m_Manager->alignModule())
2246  {
2247  // Align State
2248  QJsonObject alignState =
2249  {
2250  {"status", getAlignStatusString(m_Manager->alignModule()->status(), false)}
2251  };
2252  sendResponse(commands[NEW_ALIGN_STATE], alignState);
2253 
2254  // Align settings
2255  sendResponse(commands[ALIGN_SET_SETTINGS], m_Manager->alignModule()->getSettings());
2256 
2257  Ekos::PolarAlignmentAssistant *paa = m_Manager->alignModule()->polarAlignmentAssistant();
2258  if (paa)
2259  {
2260  // Polar State
2261  QTextDocument doc;
2262  doc.setHtml(paa->getPAHMessage());
2263  QJsonObject polarState =
2264  {
2265  {"stage", paa->getPAHStageString(false)},
2266  {"enabled", paa->isEnabled()},
2267  {"message", doc.toPlainText()},
2268  };
2269  sendResponse(commands[NEW_POLAR_STATE], polarState);
2270 
2271 
2272  // Polar settings
2273  sendResponse(commands[PAH_SET_SETTINGS], paa->getPAHSettings());
2274  }
2275  }
2276 }
2277 
2278 void Message::sendEvent(const QString &message, KSNotification::EventType event)
2279 {
2280  if (m_isConnected == false || m_Options[OPTION_SET_NOTIFICATIONS] == false)
2281  return;
2282 
2283  QJsonObject newEvent = {{ "severity", event}, {"message", message}, {"uuid", QUuid::createUuid().toString()}};
2284  sendResponse(commands[NEW_NOTIFICATION], newEvent);
2285 }
2286 
2287 void Message::sendManualRotatorStatus(double currentPA, double targetPA, double threshold)
2288 {
2289  if (m_isConnected == false)
2290  return;
2291 
2292  QJsonObject request = {{ "currentPA", currentPA}, {"targetPA", targetPA}, {"threshold", threshold}};
2293  sendResponse(commands[ALIGN_MANUAL_ROTATOR_STATUS], request);
2294 }
2295 
2296 void Message::setBoundingRect(QRect rect, QSize view, double currentZoom)
2297 {
2298  m_BoundingRect = rect;
2299  m_ViewSize = view;
2300  m_CurrentZoom = currentZoom;
2301 }
2302 
2303 void Message::processDialogResponse(const QJsonObject &payload)
2304 {
2305  KSMessageBox::Instance()->selectResponse(payload["button"].toString());
2306 }
2307 
2308 void Message::processNewProperty(INDI::Property prop)
2309 {
2310  // Do not send new properties until all properties settle down
2311  // then send any properties that appears afterwards since the initial bunch
2312  // would cause a heavy message congestion.
2313  if (m_Manager->settleStatus() != Ekos::CommunicationStatus::Success)
2314  return;
2315 
2316  QJsonObject propObject;
2317  ISD::propertyToJson(prop, propObject, false);
2318  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_ADD]}, {"payload", propObject}}).toJson(
2320 }
2321 
2322 void Message::processDeleteProperty(const QString &device, const QString &name)
2323 {
2324  QJsonObject payload =
2325  {
2326  {"device", device},
2327  {"name", name}
2328  };
2329 
2330  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_REMOVE]}, {"payload", payload}}).toJson(
2332 }
2333 
2334 void Message::processNewNumber(INumberVectorProperty * nvp)
2335 {
2336  if (m_PropertySubscriptions.contains(nvp->device))
2337  {
2338  QSet<QString> subProps = m_PropertySubscriptions[nvp->device];
2339  if (subProps.contains(nvp->name))
2340  {
2341  QJsonObject * propObject = new QJsonObject();
2342  ISD::propertyToJson(nvp, *propObject);
2343 
2344  if (m_PropertyCache.contains(nvp->name) && *m_PropertyCache[nvp->name] == *propObject)
2345  {
2346  delete (propObject);
2347  return;
2348  }
2349 
2350  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_GET]}, {"payload", *propObject}}).toJson(
2352  m_PropertyCache.insert(nvp->name, propObject);
2353  }
2354  }
2355 }
2356 
2357 void Message::processNewText(ITextVectorProperty * tvp)
2358 {
2359  if (m_PropertySubscriptions.contains(tvp->device))
2360  {
2361  QSet<QString> subProps = m_PropertySubscriptions[tvp->device];
2362  if (subProps.contains(tvp->name))
2363  {
2364  QJsonObject * propObject = new QJsonObject();
2365  ISD::propertyToJson(tvp, *propObject);
2366 
2367  if (m_PropertyCache.contains(tvp->name) && *m_PropertyCache[tvp->name] == *propObject)
2368  {
2369  delete (propObject);
2370  return;
2371  }
2372 
2373  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_GET]}, {"payload", *propObject}}).toJson(
2375  m_PropertyCache.insert(tvp->name, propObject);
2376  }
2377  }
2378 }
2379 
2380 void Message::processNewSwitch(ISwitchVectorProperty * svp)
2381 {
2382  if (m_PropertySubscriptions.contains(svp->device))
2383  {
2384  QSet<QString> subProps = m_PropertySubscriptions[svp->device];
2385  if (subProps.contains(svp->name))
2386  {
2387  QJsonObject * propObject = new QJsonObject();
2388  ISD::propertyToJson(svp, *propObject);
2389 
2390  if (m_PropertyCache.contains(svp->name) && *m_PropertyCache[svp->name] == *propObject)
2391  {
2392  delete (propObject);
2393  return;
2394  }
2395 
2396  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_GET]}, {"payload", *propObject}}).toJson(
2398  m_PropertyCache.insert(svp->name, propObject);
2399  }
2400  }
2401 }
2402 
2403 void Message::processNewLight(ILightVectorProperty * lvp)
2404 {
2405  if (m_PropertySubscriptions.contains(lvp->device))
2406  {
2407  QSet<QString> subProps = m_PropertySubscriptions[lvp->device];
2408  if (subProps.contains(lvp->name))
2409  {
2410  QJsonObject propObject;
2411  ISD::propertyToJson(lvp, propObject);
2412  m_WebSocket.sendTextMessage(QJsonDocument({{"type", commands[DEVICE_PROPERTY_GET]}, {"payload", propObject}}).toJson(
2414  }
2415  }
2416 }
2417 
2418 void Message::sendModuleState(const QString &name)
2419 {
2420  if (m_isConnected == false)
2421  return;
2422 
2423  if (name == "Capture")
2424  {
2425  QJsonObject captureState = {{ "status", getCaptureStatusString(m_Manager->captureModule()->status(), false)}};
2426  sendResponse(commands[NEW_CAPTURE_STATE], captureState);
2427  sendCaptureSequence(m_Manager->captureModule()->getSequence());
2428  }
2429  else if (name == "Mount")
2430  {
2431  QJsonObject mountState =
2432  {
2433  {"status", m_Manager->mountStatus->text()},
2434  {"target", m_Manager->mountTarget->text()},
2435  {"slewRate", m_Manager->mountModule()->slewRate()},
2436  {"pierSide", m_Manager->mountModule()->pierSide()}
2437  };
2438 
2439  sendResponse(commands[NEW_MOUNT_STATE], mountState);
2440  }
2441  else if (name == "Focus")
2442  {
2443  QJsonObject focusState = {{ "status", getFocusStatusString(m_Manager->focusModule()->status(), false)}};
2444  sendResponse(commands[NEW_FOCUS_STATE], focusState);
2445  }
2446  else if (name == "Guide")
2447  {
2448  QJsonObject guideState = {{ "status", getGuideStatusString(m_Manager->guideModule()->status(), false)}};
2449  sendResponse(commands[NEW_GUIDE_STATE], guideState);
2450  }
2451  else if (name == "Align")
2452  {
2453  // Align State
2454  QJsonObject alignState =
2455  {
2456  {"status", getAlignStatusString(m_Manager->alignModule()->status(), false)}
2457  };
2458  sendResponse(commands[NEW_ALIGN_STATE], alignState);
2459 
2460  // Align settings
2461  sendResponse(commands[ALIGN_SET_SETTINGS], m_Manager->alignModule()->getSettings());
2462 
2463  Ekos::PolarAlignmentAssistant *paa = m_Manager->alignModule()->polarAlignmentAssistant();
2464  if (paa)
2465  {
2466  // Polar State
2467  QTextDocument doc;
2468  doc.setHtml(paa->getPAHMessage());
2469  QJsonObject polarState =
2470  {
2471  {"stage", paa->getPAHStageString(false)},
2472  {"enabled", paa->isEnabled()},
2473  {"message", doc.toPlainText()},
2474  };
2475  sendResponse(commands[NEW_POLAR_STATE], polarState);
2476 
2477  // Polar settings
2478  sendResponse(commands[PAH_SET_SETTINGS], paa->getPAHSettings());
2479  }
2480  }
2481 }
2482 
2483 }
void append(const T &value)
const dms & alt() const
Definition: skypoint.h:281
const QJsonArray & getSequence() const
getSequence Return the JSON representation of the current sequeue queue
Definition: capture.h:366
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
void captureOne()
captureOne Capture one preview image
Definition: capture.cpp:2434
Direction
QAction * action(const QString &name) const
std::optional< QSqlQuery > query(const QString &queryStatement)
Q_SCRIPTABLE Q_NOREPLY void capture(double settleTime=0.0)
DBUS interface function.
Definition: focus.cpp:1229
KStarsDateTime addDays(int nd) const
Modify the Date/Time by adding a number of days.
Q_SCRIPTABLE Q_NOREPLY void abort()
DBUS interface function.
Definition: focus.cpp:1161
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
Supports controlling INDI telescope devices including setting/retrieving mount properties,...
Definition: mount.h:31
bool remove(const T &value)
const KStarsDateTime & lt() const
Definition: kstarsdata.h:150
QTime fromString(const QString &string, Qt::DateFormat format)
Q_SCRIPTABLE bool captureAndSolve()
DBUS interface function.
Definition: align.cpp:1618
LocalTime
QVector::iterator begin()
void addJob()
addJob Add a new job from form values
Definition: scheduler.cpp:1106
void generateDarkFlats()
generateDarkFlats Generate a list of dark flat jobs from available flat frames.
Definition: capture.cpp:7517
bool addJob(bool preview=false, bool isDarkFlat=false)
addJob Add a new job to the sequence queue given the settings in the GUI.
Definition: capture.cpp:2970
Q_SCRIPTABLE Q_NOREPLY void stop(CaptureState targetState=CAPTURE_IDLE)
DBUS interface function.
Definition: capture.cpp:879
QDateTime currentDateTime()
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
QAbstractSocket::SocketError error() const const
a dms subclass that caches its sine and cosine values every time the angle is changed.
Definition: cachingdms.h:18
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QChar separator()
void setDelay(uint16_t delay)
setDelay Set delay between capturing images within a sequence in seconds
Definition: capture.h:544
void append(const T &value)
Q_SCRIPTABLE Q_NOREPLY void start()
DBUS interface function.
Definition: capture.cpp:746
QJsonObject fromVariantMap(const QVariantMap &map)
Q_SCRIPTABLE bool saveSequenceQueue(const QString &path)
DBUS interface function.
Definition: capture.cpp:4747
void setCurrentIndex(int index)
float mag() const
Definition: skyobject.h:206
SkyMap * map() const
Definition: kstars.h:143
static KStarsDateTime currentDateTimeUtc()
KSUserDB * userdb()
Definition: kstarsdata.h:214
bool isValid() const const
QByteArray toLatin1() const const
KGuiItem properties()
Q_SCRIPTABLE Q_NOREPLY void toggleVideo(bool enabled)
DBUS interface function.
Definition: capture.cpp:6362
void stop()
stop Abort all dark job captures.
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
Definition: skypoint.cpp:77
bool loadAndSlew(const QByteArray &image, const QString &extension)
DBUS interface function.
Definition: align.cpp:3076
bool contains(const QString &key) const const
QString toPlainText() const const
KStarsDateTime addSecs(double s) const
Q_SCRIPTABLE bool setCamera(const QString &device)
DBUS interface function.
Definition: capture.cpp:1014
static KStars * Instance()
Definition: kstars.h:125
QString tempPath()
void removeOneJob(int index)
Remove a job by selecting a table row.
Definition: scheduler.cpp:1856
float a() const
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
QJsonObject getCalibrationSettings()
getCalibrationSettings Get Calibration settings
Definition: capture.cpp:6847
Q_SCRIPTABLE Q_NOREPLY void loop()
DBUS interface function.
Definition: guide.cpp:3463
SkyObject * findByName(const QString &name, bool exact=true) override
Search the children of this SkyMapComposite for a SkyObject whose name matches the argument.
void changeDateTime(const KStarsDateTime &newDate)
Change the current simulation date/time to the KStarsDateTime argument.
Definition: kstarsdata.cpp:327
qreal angle() const const
void setCount(uint16_t count)
seqCount Set required number of images to capture in one sequence job
Definition: capture.h:535
QJsonObject::iterator insert(const QString &key, const QJsonValue &value)
const CachingDms & dec() const
Definition: skypoint.h:269
void setLimitSettings(const QJsonObject &settings)
setLimitSettings Set limit settings
Definition: capture.cpp:6864
Q_SCRIPTABLE Q_NOREPLY void start()
DBUS interface function.
Definition: focus.cpp:930
char * toString(const T &value)
Q_SCRIPTABLE bool capture()
DBUS interface function.
Definition: guide.cpp:874
Performs calibration and autoguiding using an ST4 port or directly via the INDI driver....
Definition: guide.h:50
void startFraming()
startFraming Begins continuous capture of the CCD and calculates HFR every frame.
Definition: focus.cpp:3240
void DeleteEquipment(const QString &type, const int &id)
Erase the equipment with given type and unique id Valid equipment types: "telescope",...
Definition: ksuserdb.cpp:997
QTime riseSetTime(const KStarsDateTime &dt, const GeoLocation *geo, bool rst, bool exact=true) const
Determine the time at which the point will rise or set.
Definition: skyobject.cpp:93
bool isEmpty() const const
GeoLocation * geo()
Definition: kstarsdata.h:229
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
QUuid createUuid()
QJsonArray fromStringList(const QStringList &list)
Q_INVOKABLE SimClock * clock()
Definition: kstarsdata.h:217
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
void selectFocusStarFraction(double x, double y)
selectFocusStarFraction Select the focus star based by fraction of the overall size.
Definition: focus.cpp:3347
double pa() const override
QVariantList toVariantList() const const
UniqueConnection
qreal length() const const
QJsonObject getPresetSettings()
getSettings get current capture settings as a JSON Object
Definition: capture.cpp:5067
GeoCoordinates geo(const QVariant &location)
void startFraming()
startFraming Like captureOne but repeating.
Definition: capture.cpp:2451
void disconnected()
The PolarAlignmentAssistant class.
QString toString() const const
int hour() const const
bool isEnabled() const const
void trigger()
bool contains(const T &value) const const
Q_SCRIPTABLE Q_NOREPLY void stop()
DBUS function to stop the SimClock.
Definition: simclock.cpp:104
void connected()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
void GetAllScopes(QList< OAL::Scope * > &m_scopeList)
updates the scope list with all scopes from database List is cleared and then filled with content.
Definition: ksuserdb.cpp:1076
Q_SCRIPTABLE Q_NOREPLY void clearCalibration()
DBUS interface function.
Definition: guide.cpp:1657
void AddScope(const QString &model, const QString &vendor, const QString &driver, const QString &type, const double &focalLength, const double &aperture)
Appends the scope with given details in the database.
Definition: ksuserdb.cpp:1030
T findChild(const QString &name, Qt::FindChildOptions options) const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
SkyMapComposite * skyComposite()
Definition: kstarsdata.h:165
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
int removeDuplicates()
Q_SCRIPTABLE Q_NOREPLY void start()
DBUS function to start the SimClock.
Definition: simclock.cpp:120
bool setVideoLimits(uint16_t maxBufferSize, uint16_t maxPreviewFPS)
setVideoLimits sets the buffer size and max preview fps for live preview
Definition: capture.cpp:6390
Captures single or sequence of images from a CCD. The capture class support capturing single or multi...
Definition: capture.h:83
QVector::iterator end()
float b() const
Q_SCRIPTABLE Q_NOREPLY void resetFrame()
DBUS interface function.
Definition: focus.cpp:188
virtual KActionCollection * actionCollection() const
const CachingDms & ra() const
Definition: skypoint.h:263
QJsonObject getLimitSettings()
getLimitSettings Get Limit Settings
Definition: capture.cpp:6908
Q_SCRIPTABLE bool focusIn(int ms=-1)
DBUS interface function.
Definition: focus.cpp:1392
Implement methods to find important times in a day.
Definition: ksalmanac.h:26
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isEmpty() const const
Q_SCRIPTABLE Q_NOREPLY void setUTC(const KStarsDateTime &newtime)
DBUS function to set the time of the SimClock.
Definition: simclock.cpp:143
void apparentCoord(long double jd0, long double jdf)
Computes the apparent coordinates for this SkyPoint for any epoch, accounting for the effects of prec...
Definition: skypoint.cpp:700
const CachingDms & dec0() const
Definition: skypoint.h:257
const double & Degrees() const
Definition: dms.h:141
Generic record interfaces and implementations.
Definition: cloud.cpp:23
void setHtml(const QString &html)
const char * name(StandardAction id)
QDate date() const const
const CachingDms & ra0() const
Definition: skypoint.h:251
void updateTargetCoords()
If the target is valid (see m_targetCoordValid), simply return.
Definition: align.cpp:3935
Q_SCRIPTABLE Q_NOREPLY void abort()
DBUS interface function.
Definition: align.h:422
QSet::iterator insert(const T &value)
QList::iterator begin()
The Ekos scheduler is a simple scheduler class to orchestrate automated multi object observation jobs...
Definition: scheduler.h:49
virtual QString longname(void) const
Definition: skyobject.h:164
QTextStream & center(QTextStream &stream)
QTime transitTime(const KStarsDateTime &dt, const GeoLocation *geo) const
The same iteration technique described in riseSetTime() is used here.
Definition: skyobject.cpp:239
Q_SCRIPTABLE bool setFilterWheel(const QString &device)
DBUS interface function.
Definition: capture.cpp:1606
void forceUpdate(bool now=false)
Recalculates the positions of objects in the sky, and then repaints the sky map.
Definition: skymap.cpp:1186
Align class handles plate-solving and polar alignment measurement and correction using astrometry....
Definition: align.h:73
Q_SCRIPTABLE bool focusOut(int ms=-1)
DBUS interface function.
Definition: focus.cpp:1399
void append(const QJsonValue &value)
void textMessageReceived(const QString &message)
bool removeJob(int index=-1)
removeJob Remove a job sequence from the queue
Definition: capture.cpp:3257
QString asprintf(const char *cformat,...)
QList::iterator end()
Supports manual focusing and auto focusing using relative and absolute INDI focusers.
Definition: focus.h:39
QJsonObject getFileSettings()
getFileSettings Compile file setting
Definition: capture.cpp:6809
QFuture< void > map(Sequence &sequence, MapFunctor function)
Q_SCRIPTABLE bool guide()
DBUS interface function.
Definition: guide.cpp:1373
Q_SCRIPTABLE Q_NOREPLY void clearSequenceQueue()
DBUS interface function.
Definition: capture.cpp:5331
A simple container object to hold the minimum information for a Deeb Sky Object to be drawn on the sk...
Definition: catalogobject.h:40
Information about an object in the sky.
Definition: skyobject.h:41
int minute() const const
QString message
bool selectResponse(const QString &button)
selectResponse Programatically select one the buttons in the dialog.
const QList< QKeySequence > & end()
Q_SCRIPTABLE bool loadSequenceQueue(const QString &fileURL)
DBUS interface function.
Definition: capture.cpp:4329
double Hours() const
Definition: dms.h:168
Q_SCRIPTABLE bool abort()
DBUS interface function.
Definition: guide.cpp:947
QPointF p1() const const
QPointF p2() const const
const dms & az() const
Definition: skypoint.h:275
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:55 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.