1 /*
2     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #pragma once
8 
9 #ifdef USE_QT5_INDI
10 #include <baseclientqt.h>
11 #else
12 #include <baseclient.h>
13 #endif
14 
15 #include "ui_manager.h"
16 
17 #include "ekos.h"
18 #include "manager/focusmanager.h"
19 #include "manager/guidemanager.h"
20 #include "fitsviewer/summaryfitsview.h"
21 #include "align/align.h"
22 #include "auxiliary/dome.h"
23 #include "auxiliary/weather.h"
24 #include "auxiliary/dustcap.h"
25 #include "capture/capture.h"
26 #include "focus/focus.h"
27 #include "guide/guide.h"
28 #include "indi/indistd.h"
29 #include "mount/mount.h"
30 #include "scheduler/scheduler.h"
31 #include "analyze/analyze.h"
32 #include "observatory/observatory.h"
33 #include "auxiliary/filtermanager.h"
34 #include "auxiliary/portselector.h"
35 #include "ksnotification.h"
36 // Can't use forward declaration with QPointer. QTBUG-29588
37 #include "auxiliary/opslogs.h"
38 
39 #include <QDialog>
40 #include <QHash>
41 #include <QtDBus/QtDBus>
42 
43 #include <memory>
44 
45 //! Generic record interfaces and implementations.
46 namespace EkosLive
47 {
48 class Client;
49 class Message;
50 class Media;
51 }
52 
53 class DriverInfo;
54 class ProfileInfo;
55 class KPageWidgetItem;
56 
57 /**
58  * @class Manager
59  * @short Primary class to handle all Ekos modules.
60  * The Ekos Manager class manages startup and shutdown of INDI devices and registeration of devices within Ekos Modules. Ekos module consist of \ref Ekos::Mount, \ref Ekos::Capture, \ref Ekos::Focus, \ref Ekos::Guide, and \ref Ekos::Align modules.
61  * \defgroup EkosDBusInterface "Ekos DBus Interface" provides high level functions to control devices and Ekos modules for a total robotic operation:
62  * <ul>
63  * <li>\ref CaptureDBusInterface "Capture Module DBus Interface"</li>
64  * <li>\ref FocusDBusInterface "Focus Module DBus Interface"</li>
65  * <li>\ref MountDBusInterface "Mount Module DBus Interface"</li>
66  * <li>\ref GuideDBusInterface "Guide Module DBus Interface"</li>
67  * <li>\ref AlignDBusInterface "Align Module DBus Interface"</li>
68  * <li>\ref WeatherDBusInterface "Weather DBus Interface"</li>
69  * <li>\ref DustCapDBusInterface "Dust Cap DBus Interface"</li>
70  * </ul>
71  *  For low level access to INDI devices, the \ref INDIDBusInterface "INDI Dbus Interface" provides complete access to INDI devices and properties.
72  *  Ekos Manager provides a summary of operations progress in the <i>Summary</i> section of the <i>Setup</i> tab.
73  *
74  * @author Jasem Mutlaq
75  * @version 1.8
76  */
77 namespace Ekos
78 {
79 
80 class Manager : public QDialog, public Ui::Manager
81 {
82         Q_OBJECT
83         Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos")
84 
85         Q_SCRIPTABLE Q_PROPERTY(Ekos::CommunicationStatus indiStatus READ indiStatus NOTIFY indiStatusChanged)
86         Q_SCRIPTABLE Q_PROPERTY(Ekos::CommunicationStatus ekosStatus READ ekosStatus NOTIFY ekosStatusChanged)
87         Q_SCRIPTABLE Q_PROPERTY(Ekos::CommunicationStatus settleStatus READ settleStatus NOTIFY settleStatusChanged)
88         Q_SCRIPTABLE Q_PROPERTY(bool ekosLiveStatus READ ekosLiveStatus NOTIFY ekosLiveStatusChanged)
89         Q_SCRIPTABLE Q_PROPERTY(QStringList logText READ logText NOTIFY newLog)
90 
91         enum class EkosModule
92         {
93             Setup,
94             Scheduler,
95             Analyze,
96             Capture,
97             Focus,
98             Mount,
99             Align,
100             Guide,
101             Observatory,
102         };
103     public:
104         static Manager *Instance();
105         static void release();
106 
107         // No OP
initialize()108         void initialize() {}
109 
110         void appendLogText(const QString &);
setOptionsWidget(KPageWidgetItem * ops)111         void setOptionsWidget(KPageWidgetItem *ops)
112         {
113             ekosOptionsWidget = ops;
114         }
115         void addObjectToScheduler(SkyObject *object);
116 
schedulerModule()117         Scheduler *schedulerModule()
118         {
119             return schedulerProcess.get();
120         }
guideModule()121         Guide *guideModule()
122         {
123             return guideProcess.get();
124         }
alignModule()125         Align *alignModule()
126         {
127             return alignProcess.get();
128         }
mountModule()129         Mount *mountModule()
130         {
131             return mountProcess.get();
132         }
focusModule()133         Focus *focusModule()
134         {
135             return focusProcess.get();
136         }
domeModule()137         Dome  *domeModule()
138         {
139             return domeProcess.get();
140         }
capModule()141         DustCap *capModule()
142         {
143             return dustCapProcess.get();
144         }
captureModule()145         Capture *captureModule()
146         {
147             return captureProcess.get();
148         }
getSummaryPreview()149         FITSView *getSummaryPreview()
150         {
151             return summaryPreview.get();
152         }
getFilterManager()153         FilterManager *getFilterManager()
154         {
155             return filterManager.data();
156         }
157         QString getCurrentJobName();
158         void announceEvent(const QString &message, KSNotification::EventType event);
159 
160         /**
161          * @brief addProfile Add a new profile to the database.
162          * @param profileInfo Collection of profile parameters to include the following:
163          * 1. name: Profile name
164          * 2. auto_connect: True of False for Autoconnect?
165          * 3. Mode: "local" or "remote"
166          * 4. remote_host: Optional. remote host (default localhost)
167          * 5. remote_port: Optional. remote port (default 7624)
168          * 6. guiding: 0 for "Internal", 1 for "PHD2", or 2 for "LinGuider"
169          * 7. remote_guiding_host: Optional. remote host for guider application (default localhost)
170          * 8. remote_guide_port: Optional. remote port for guider application.
171          * 9. use_web_manager: True or False?
172          * 10. web_manager_port. Optional. INDI Web Manager port (default 8624)
173          * 12. primary_scope: ID of primary scope to use. This is the ID from OAL::Scope list in the database.
174          * 13. guide_scope: ID of guide scope to use. This is the ID from OAL::Scope list in the database.
175          * 14. mount: Mount driver label (default --).
176          * 15. ccd: CCD driver label (default --).
177          * 16. guider: Guider driver label (default --).
178          * 17. focuser: Focuser driver label (default --).
179          * 18. filter: Filter Wheel driver label (default --).
180          * 19. ao: Adaptive Optics driver label (default --).
181          * 20. dome: Dome driver label (default --).
182          * 21. Weather: Weather station driver label (default --).
183          * 22. aux1: aux1 driver label (default --).
184          * 23. aux2: aux2 driver label (default --).
185          * 24. aux3: aux3 driver label (default --).
186          * 25. aux4: aux4 driver label (default --).
187          */
188         void addNamedProfile(const QJsonObject &profileInfo);
189 
190         /** Same as above, except it edits an existing named profile */
191         void editNamedProfile(const QJsonObject &profileInfo);
192 
193         /**
194          * @brief deleteProfile Delete existing equipment profile
195          * @param name Name of profile
196          * @warning Ekos must be stopped for this to work. It will fail if Ekos is online.
197          */
198         void deleteNamedProfile(const QString &name);
199 
200         /**
201          * @brief getProfile Get a single profile information.
202          * @param name Profile name
203          * @return A JSon object with the detail profile info as described in addProfile function.
204          */
205         QJsonObject getNamedProfile(const QString &name);
206 
207         /**
208          * DBus commands to manage equipment profiles.
209          */
210 
211         /*@{*/
212 
213         /**
214          * DBUS interface function.
215          * set Current device profile.
216          * @param profileName Profile name
217          * @return True if profile is set, false if not found.
218          */
219         Q_SCRIPTABLE bool setProfile(const QString &profileName);
220 
221         /**
222          * DBUS interface function
223          * @brief getProfiles Return a list of all device profiles
224          * @return List of device profiles
225          */
226         Q_SCRIPTABLE QStringList getProfiles();
227 
228         /** @}*/
229 
230 
231         /**
232          * Manager interface provides advanced scripting capabilities to establish and shutdown Ekos services.
233          */
234 
235         /*@{*/
236 
237         /**
238          * DBUS interface function.
239          * @return INDI connection status (0 Idle, 1 Pending, 2 Connected, 3 Error)
240          * @deprecated
241          */
getINDIConnectionStatus()242         Q_SCRIPTABLE unsigned int getINDIConnectionStatus()
243         {
244             return m_indiStatus;
245         }
246 
indiStatus()247         Q_SCRIPTABLE Ekos::CommunicationStatus indiStatus()
248         {
249             return m_indiStatus;
250         }
251 
252         /**
253          * DBUS interface function.
254          * @return Ekos starting status (0 Idle, 1 Pending, 2 Started, 3 Error)
255          * @deprecated
256          */
getEkosStartingStatus()257         Q_SCRIPTABLE unsigned int getEkosStartingStatus()
258         {
259             return m_ekosStatus;
260         }
261 
ekosStatus()262         Q_SCRIPTABLE Ekos::CommunicationStatus ekosStatus()
263         {
264             return m_ekosStatus;
265         }
266 
267         /**
268          * DBUS interface function.
269          * @return Settle status (0 Idle, 1 Pending, 2 Started, 3 Error)
270          */
settleStatus()271         Q_SCRIPTABLE Ekos::CommunicationStatus settleStatus()
272         {
273             return m_settleStatus;
274         }
275 
276         /**
277          * DBUS interface function. Toggle Ekos logging.
278          * @param name Name of logging to toggle. Available options are:
279          * ** VERBOSE
280          * ** INDI
281          * ** FITS
282          * ** CAPTURE
283          * ** FOCUS
284          * ** GUIDE
285          * ** ALIGNMENT
286          * ** MOUNT
287          * ** SCHEDULER
288          * ** OBSERVATORY
289          * @param enabled True to enable, false otherwise.
290          */
291         Q_SCRIPTABLE Q_NOREPLY void setEkosLoggingEnabled(const QString &name, bool enabled);
292 
293         /**
294          * DBUS interface function.
295          * If connection mode is local, the function first establishes an INDI server with all the specified drivers in Ekos options or as set by the user. For remote connection,
296          * it establishes connection to the remote INDI server.
297          * @return Returns true if server started successful (local mode) or connection to remote server is successful (remote mode).
298          */
299         Q_SCRIPTABLE void start();
300 
301         /**
302          * DBUS interface function.
303          * If connection mode is local, the function terminates the local INDI server and drivers. For remote, it disconnects from the remote INDI server.
304          */
305         Q_SCRIPTABLE void stop();
306 
logText()307         Q_SCRIPTABLE QStringList logText()
308         {
309             return m_LogText;
310         }
311 
312         Q_SCRIPTABLE bool ekosLiveStatus();
313 
314         /**
315          * DBUS interface function.
316          * @param enabled Connect to EkosLive if true, otherwise disconnect.
317         */
318         Q_SCRIPTABLE void setEkosLiveConnected(bool enabled);
319 
320         /**
321          * @brief setEkosLiveConfig Set EkosLive settings
322          * @param onlineService If true, connect to EkosLive Online Service. Otherwise, EkosLive offline service.
323          * @param rememberCredentials Remember username and password for next session.
324          * @param autoConnect If true, it will automatically connect to EkosLive service.
325          */
326         Q_SCRIPTABLE void setEkosLiveConfig(bool onlineService, bool rememberCredentials, bool autoConnect);
327 
328         /**
329          * @brief setEkosLiveUser Save EkosLive username and password
330          * @param username User name
331          * @param password Password
332          */
333         Q_SCRIPTABLE void setEkosLiveUser(const QString &username, const QString &password);
334 
335         /**
336          * @brief acceptPortSelection Accept current port selection settings in the Selector Dialog
337          */
338         Q_SCRIPTABLE void acceptPortSelection();
339 
340     signals:
341         // Have to use full Ekos::CommunicationStatus for DBus signal to work
342         void ekosStatusChanged(Ekos::CommunicationStatus status);
343         void indiStatusChanged(Ekos::CommunicationStatus status);
344         void settleStatusChanged(Ekos::CommunicationStatus status);
345         void ekosLiveStatusChanged(bool status);
346 
347         void newLog(const QString &text);
348         void newModule(const QString &name);
349 
350     protected:
351         void closeEvent(QCloseEvent *event) override;
352         void hideEvent(QHideEvent *) override;
353         void showEvent(QShowEvent *) override;
354         void resizeEvent(QResizeEvent *) override;
355 
356     public slots:
357 
358         /**
359          * DBUS interface function.
360          * Connects all the INDI devices started by Ekos.
361          */
362         Q_SCRIPTABLE Q_NOREPLY void connectDevices();
363 
364         /**
365          * DBUS interface function.
366          * Disconnects all the INDI devices started by Ekos.
367          */
368         Q_SCRIPTABLE Q_NOREPLY void disconnectDevices();
369 
370         /** @}*/
371 
372         void processINDI();
373         void cleanDevices(bool stopDrivers = true);
374 
375         void processNewDevice(ISD::GDInterface *);
376         void processNewProperty(INDI::Property);
377         void processDeleteProperty(const QString &name);
378 
379         void processNewNumber(INumberVectorProperty *nvp);
380         void processNewText(ITextVectorProperty *tvp);
381         void processNewSwitch(ISwitchVectorProperty *svp);
382         void processNewLight(ILightVectorProperty *lvp);
383         void processNewBLOB(IBLOB *bvp);
384 
385         void restartDriver(const QString &deviceName);
386 
387     private slots:
388 
389         void changeAlwaysOnTop(Qt::ApplicationState state);
390 
391         void showEkosOptions();
392 
393         void updateLog();
394         void clearLog();
395 
396         void processTabChange();
397 
398         void processServerTerminated(const QString &host, const QString &port);
399         void processServerStarted(const QString &host, const QString &port);
400 
401         void removeDevice(ISD::GDInterface *);
402 
403         void deviceConnected();
404         void deviceDisconnected();
405 
406         //void processINDIModeChange();
407         void checkINDITimeout();
408 
409         // Logs
410         void updateDebugInterfaces();
411         void watchDebugProperty(ISwitchVectorProperty *svp);
412 
413         void setTelescope(ISD::GDInterface *);
414         void setCCD(ISD::GDInterface *);
415         void setFilter(ISD::GDInterface *);
416         void setFocuser(ISD::GDInterface *);
417         void setDome(ISD::GDInterface *);
418         void setWeather(ISD::GDInterface *);
419         void setDustCap(ISD::GDInterface *);
420         void setLightBox(ISD::GDInterface *);
421         void setST4(ISD::ST4 *);
422 
423         // Profiles
424         void addProfile();
425         void editProfile();
426         void deleteProfile();
427         void wizardProfile();
428 
429         // Mount Summary
430         void updateMountCoords(const SkyPoint position, ISD::Telescope::PierSide pierSide, const dms &ha);
431         void updateMountStatus(ISD::Telescope::Status status);
432         void setTarget(SkyObject *o);
433 
434         // Capture Summary
435         void updateCaptureStatus(CaptureState status);
436         void updateCaptureProgress(SequenceJob *job, const QSharedPointer<FITSData> &data);
437         void updateExposureProgress(SequenceJob *job);
438         void updateCaptureCountDown();
439 
440         // Focus summary
441         void updateFocusStatus(FocusState status);
442         void updateCurrentHFR(double newHFR, int position);
getFocusStatusText()443         const QString getFocusStatusText()
444         {
445             return focusManager->focusStatus->text();
446         }
447 
448         // Guide Summary
449         void updateGuideStatus(GuideState status);
450         void updateSigmas(double ra, double de);
getGuideStatusText()451         const QString getGuideStatusText()
452         {
453             return guideManager->guideStatus->text();
454         }
455 
456     private:
457         explicit Manager(QWidget *parent);
458         ~Manager() override;
459 
460         void removeTabs();
461         void reset();
462         void initCapture();
463         void initFocus();
464         void initGuide();
465         void initAlign();
466         void initMount();
467         void initDome();
468         void initWeather();
469         void initObservatory(Weather *weather, Dome *dome);
470         void initDustCap();
471 
472         void loadDrivers();
473         void loadProfiles();
474         int addModuleTab(EkosModule module, QWidget *tab, const QIcon &icon);
475 
476         /**
477          * @brief syncActiveDevices Syncs ACTIVE_DEVICES such as ACTIVE_TELESCOPE and ACTIVE_CCD
478          * to the currently detected devices.
479          */
480         void syncActiveDevices();
481 
482         // Connect Signals/Slots of Ekos modules
483         void connectModules();
484 
485         // Check if INDI server is already running
486         bool isRunning(const QString &process);
487 
488         // Find List of devices of specific family type
489         QList<ISD::GDInterface *> findDevices(DeviceFamily type);
490         // Find list of devices by device interface
491         QList<ISD::GDInterface *> findDevicesByInterface(uint32_t interface);
492         // Get all detected devices
493         const QList<ISD::GDInterface *> &getAllDevices() const;
494 
495         ProfileInfo *getCurrentProfile();
496         void getCurrentProfileTelescopeInfo(double &primaryFocalLength, double &primaryAperture, double &guideFocalLength,
497                                             double &guideAperture);
498         void updateProfileLocation(ProfileInfo *pi);
setProfileMapping(const QJsonObject & payload)499         void setProfileMapping(const QJsonObject &payload)
500         {
501             m_ProfileMapping = payload;
502         }
503         // Port Selector Save profile when connect all is pressed
504         void setPortSelectionComplete();
505         // Check if the driver binary must be one only to avoid duplicate instances
506         // Some driver binaries support multiple devices per binary
507         // so we only need to start a single instance to handle them all.
508         bool checkUniqueBinaryDriver(DriverInfo * primaryDriver, DriverInfo * secondaryDriver);
509 
510         bool useGuideHead { false };
511         bool useST4 { false };
512 
513         // Containers
514 
515         // All Drivers
516         QHash<QString, DriverInfo *> driversList;
517 
518         // All managed drivers
519         QList<DriverInfo *> managedDrivers;
520 
521         // All generic devices (i.e. those define by INDI server)
522         QList<ISD::GDInterface *> genericDevices;
523 
524         // All proxy devices (generated devices by Ekos Manager for specific interfaces)
525         QList<ISD::GDInterface *> proxyDevices;
526 
527         // All Managed devices (ie. those explicitly defined in the profile)
528         QMap<DeviceFamily, ISD::GDInterface *> managedDevices;
529 
530         // Smart pointers for the various Ekos Modules
531         std::unique_ptr<Capture> captureProcess;
532         std::unique_ptr<Focus> focusProcess;
533         std::unique_ptr<Guide> guideProcess;
534         std::unique_ptr<Align> alignProcess;
535         std::unique_ptr<Mount> mountProcess;
536         std::unique_ptr<Analyze> analyzeProcess;
537         std::unique_ptr<Scheduler> schedulerProcess;
538         std::unique_ptr<Observatory> observatoryProcess;
539         std::unique_ptr<Dome> domeProcess;
540         std::unique_ptr<Weather> weatherProcess;
541         std::unique_ptr<DustCap> dustCapProcess;
542         std::unique_ptr<EkosLive::Client> ekosLiveClient;
543 
544         bool m_LocalMode { true };
545         bool m_isStarted { false };
546         bool m_RemoteManagerStart { false };
547 
548         int nDevices { 0 };
549 
550         QStringList m_LogText;
551         KPageWidgetItem *ekosOptionsWidget { nullptr };
552 
553         CommunicationStatus m_ekosStatus { Ekos::Idle };
554         CommunicationStatus m_indiStatus { Ekos::Idle };
555         // Settle is used to know once all properties from all devices have been defined
556         // There is no way to know this for sure so we use a debounace mechanism.
557         CommunicationStatus m_settleStatus { Ekos::Idle };
558 
559         std::unique_ptr<QStandardItemModel> profileModel;
560         QList<std::shared_ptr<ProfileInfo>> profiles;
561         QJsonObject m_ProfileMapping;
562 
563         // Filter Manager
564         QSharedPointer<FilterManager> filterManager;
565 
566         // Mount Summary
567         QPointer<QProcess> indiHubAgent;
568 
569         // Capture Summary
570         QTimer m_CountdownTimer;
571         QTimer settleTimer;
572         // Preview Frame
573         std::unique_ptr<SummaryFITSView> summaryPreview;
574 
575         ProfileInfo *currentProfile { nullptr };
576         bool profileWizardLaunched { false };
577 
578         // Port Selector
579         std::unique_ptr<Selector::Dialog> m_PortSelector;
580         QTimer m_PortSelectorTimer;
581 
582         // Logs
583         QPointer<OpsLogs> opsLogs;
584 
585         // E.g. Setup, Scheduler, and Analyze.
586         int numPermanentTabs { 0 };
587 
588         friend class EkosLive::Client;
589         friend class EkosLive::Message;
590         friend class EkosLive::Media;
591 
592         static Manager *_Manager;
593 };
594 
595 }
596