Kstars

focusmodule.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "focusmodule.h"
8#include "focus.h"
9#include "focusadaptor.h"
10
11#include "Options.h"
12#include "auxiliary/ksmessagebox.h"
13#include "ekos/auxiliary/opticaltrainmanager.h"
14#include "kstarsdata.h"
15#include "kspaths.h"
16#include <KConfigDialog>
17
18#include "ekos_focus_debug.h"
19
20#define TAB_BUTTON_SIZE 20
21
22namespace Ekos
23{
24
25FocusModule::FocusModule()
26{
27 setupUi(this);
28
29 focusTabs->setTabsClosable(true);
30 // Connect the close request signal to the slot
31 connect(focusTabs, &QTabWidget::tabCloseRequested, this, &FocusModule::checkCloseFocuserTab);
32 // Adding the "New Tab" tab
33 QWidget *newTab = new QWidget;
34 QPushButton *addButton = new QPushButton;
35 addButton->setIcon(QIcon::fromTheme("list-add"));
36 addButton->setFixedSize(TAB_BUTTON_SIZE, TAB_BUTTON_SIZE);
37 addButton->setToolTip(i18n("<p>Add additional focuser</p><p><b>WARNING</b>: This feature is experimental!</p>"));
38 connect(addButton, &QPushButton::clicked, this, [this]()
39 {
40 FocusModule::addFocuser();
41 });
42
43 focusTabs->addTab(newTab, "");
44 focusTabs->tabBar()->setTabButton(0, QTabBar::RightSide, addButton);
45
46 // Create an autofocus CSV file, dated at startup time
47 m_FocusLogFileName = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("focuslogs/autofocus-" +
48 QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss") + ".txt");
49 m_FocusLogFile.setFileName(m_FocusLogFileName);
50
51 // Create main focuser
52 addFocuser();
53
54 // Register DBus
55 qRegisterMetaType<FocusState>("FocusState");
56 qDBusRegisterMetaType<FocusState>();
57 new FocusAdaptor(this);
58 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Focus", this);
59}
60
61FocusModule::~FocusModule()
62{
63 m_FocusLogFile.close();
64}
65
66QSharedPointer<Focus> &FocusModule::focuser(int i)
67{
68 if (i < m_Focusers.count())
69 return m_Focusers[i];
70 else
71 {
72 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << i;
73 return m_Focusers[0];
74 }
75
76}
77
78QString FocusModule::filterWheel(const QString &trainname)
79{
80 int id = findFocuser(trainname, true);
81 if (0 <= id && id < m_Focusers.count())
82 return m_Focusers[id]->filterWheel();
83 else
84 {
85 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
86 return "";
87 }
88}
89
90bool FocusModule::setFilter(const QString &filter, const QString &trainname)
91{
92 int id = findFocuser(trainname, true);
93 if (0 <= id && id < m_Focusers.count())
94 return m_Focusers[id]->setFilter(filter);
95 else
96 {
97 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
98 return false;
99 }
100}
101
102QString FocusModule::filter(const QString &trainname)
103{
104 int id = findFocuser(trainname, true);
105 if (0 <= id && id < m_Focusers.count())
106 return m_Focusers[id]->filter();
107 else
108 {
109 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
110 return "";
111 }
112}
113
114double FocusModule::getHFR(const QString &trainname)
115{
116 int id = findFocuser(trainname, true);
117 if (0 <= id && id < m_Focusers.count())
118 return m_Focusers[id]->getHFR();
119 else
120 {
121 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
122 return -1.0;
123 }
124}
125
126bool FocusModule::setExposure(double value, const QString &trainname)
127{
128 int id = findFocuser(trainname, true);
129 if (0 <= id && id < m_Focusers.count())
130 {
131 m_Focusers[id]->setExposure(value);
132 return true;
133 }
134 else
135 {
136 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
137 return false;
138 }
139}
140
141double FocusModule::exposure(const QString &trainname)
142{
143 int id = findFocuser(trainname, true);
144 if (0 <= id && id < m_Focusers.count())
145 return m_Focusers[id]->exposure();
146 else
147 {
148 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
149 return -1.0;
150 }
151}
152
153bool FocusModule::canAutoFocus(const QString &trainname)
154{
155 int id = findFocuser(trainname, true);
156 if (0 <= id && id < m_Focusers.count())
157 return m_Focusers[id]->canAutoFocus();
158 else
159 {
160 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
161 return false;
162 }
163}
164
165bool FocusModule::useFullField(const QString &trainname)
166{
167 int id = findFocuser(trainname, true);
168 if (0 <= id && id < m_Focusers.count())
169 return m_Focusers[id]->useFullField();
170 else
171 {
172 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
173 return false;
174 }
175}
176
177bool FocusModule::setBinning(int binX, int binY, const QString &trainname)
178{
179 int id = findFocuser(trainname, true);
180 if (0 <= id && id < m_Focusers.count())
181 {
182 m_Focusers[id]->setBinning(binX, binY);
183 return true;
184 }
185 else
186 {
187 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
188 return false;
189 }
190}
191
192bool FocusModule::setAutoStarEnabled(bool enable, const QString &trainname)
193{
194 int id = findFocuser(trainname, true);
195 if (0 <= id && id < m_Focusers.count())
196 {
197 m_Focusers[id]->setAutoStarEnabled(enable);
198 return true;
199 }
200 else
201 {
202 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
203 return false;
204 }
205}
206
207bool FocusModule::setAutoSubFrameEnabled(bool enable, const QString &trainname)
208{
209 int id = findFocuser(trainname, true);
210 if (0 <= id && id < m_Focusers.count())
211 {
212 m_Focusers[id]->setAutoSubFrameEnabled(enable);
213 return true;
214 }
215 else
216 {
217 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
218 return false;
219 }
220}
221
222bool FocusModule::setAutoFocusParameters(const QString &trainname, int boxSize, int stepSize, int maxTravel,
223 double tolerance)
224{
225 int id = findFocuser(trainname, true);
226 if (0 <= id && id < m_Focusers.count())
227 {
228 m_Focusers[id]->setAutoFocusParameters(boxSize, stepSize, maxTravel, tolerance);
229 return true;
230 }
231 else
232 {
233 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
234 return false;
235 }
236}
237
238QSharedPointer<Focus> FocusModule::mainFocuser()
239{
240 if (m_Focusers.size() <= 0)
241 {
242 QSharedPointer<Focus> newFocuser;
243 newFocuser.reset(new Focus(0));
244 m_Focusers.append(newFocuser);
245 }
246 return m_Focusers[0];
247}
248
249int FocusModule::findFocuser(const QString &trainname, bool addIfNecessary)
250{
251 for (int pos = 0; pos < m_Focusers.count(); pos++)
252 if (m_Focusers[pos]->opticalTrain() == trainname)
253 return pos;
254
255 if (addIfNecessary)
256 {
257 addFocuser(trainname);
258 return m_Focusers.count() - 1;
259 }
260 else
261 return -1;
262}
263
264void FocusModule::checkFocus(double requiredHFR, const QString &trainname)
265{
266 bool found = false;
267 // publish to all known focusers using the same optical train (should be only one)
268 for (auto focuser : m_Focusers)
269 if (trainname == "" || focuser->opticalTrain() == trainname)
270 {
271 focuser->checkFocus(requiredHFR);
272 found = true;
273 }
274
275 if (!found)
276 {
277 QSharedPointer newFocuser = addFocuser(trainname);
278 newFocuser->checkFocus(requiredHFR);
279 }
280}
281
282void FocusModule::runAutoFocus(const AutofocusReason autofocusReason, const QString &reasonInfo, const QString &trainname)
283{
284 bool found = false;
285 // publish to all known focusers using the same optical train (should be only one)
286 for (auto focuser : m_Focusers)
287 if (trainname == "" || focuser->opticalTrain() == trainname)
288 {
289 focuser->runAutoFocus(autofocusReason, reasonInfo);
290 found = true;
291 }
292
293 if (!found)
294 {
295 QSharedPointer newFocuser = addFocuser(trainname);
296 newFocuser->runAutoFocus(autofocusReason, reasonInfo);
297 }
298}
299
300void FocusModule::resetFrame(const QString &trainname)
301{
302 bool found = false;
303 // publish to all known focusers using the same optical train (should be only one)
304 for (auto focuser : m_Focusers)
305 if (trainname == "" || focuser->opticalTrain() == trainname)
306 {
307 focuser->resetFrame();
308 found = true;
309 }
310
311 if (!found)
312 {
313 QSharedPointer newFocuser = addFocuser(trainname);
314 newFocuser->resetFrame();
315 }
316}
317
318void FocusModule::abort(const QString &trainname)
319{
320 bool found = false;
321 // publish to all known focusers using the same optical train (should be only one)
322 for (auto focuser : m_Focusers)
323 if (trainname == "" || focuser->opticalTrain() == trainname)
324 {
325 focuser->abort();
326 found = true;
327 }
328
329 if (!found)
330 {
331 QSharedPointer newFocuser = addFocuser(trainname);
332 newFocuser->abort();
333 }
334}
335
336void FocusModule::adaptiveFocus(const QString &trainname)
337{
338 bool found = false;
339 // publish to all known focusers using the same optical train (should be only one)
340 for (auto focuser : m_Focusers)
341 if (trainname == "" || focuser->opticalTrain() == trainname)
342 {
343 focuser->adaptiveFocus();
344 found = true;
345 }
346
347 if (!found)
348 {
349 QSharedPointer newFocuser = addFocuser(trainname);
350 newFocuser->adaptiveFocus();
351 }
352}
353
354void FocusModule::meridianFlipStarted(const QString &trainname)
355{
356 bool found = false;
357 // publish to all known focusers using the same optical train (should be only one)
358 for (auto focuser : m_Focusers)
359 if (trainname == "" || focuser->opticalTrain() == trainname)
360 {
361 focuser->meridianFlipStarted();
362 found = true;
363 }
364
365 if (!found)
366 {
367 QSharedPointer newFocuser = addFocuser(trainname);
368 newFocuser->meridianFlipStarted();
369 }
370}
371
372void FocusModule::setMountStatus(ISD::Mount::Status newState)
373{
374 // publish to all known focusers using the same optical train (should be only one)
375 for (auto focuser : m_Focusers)
376 focuser->setMountStatus(newState);
377}
378
379void FocusModule::setMountCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
380{
381 // publish to all known focusers using the same optical train (should be only one)
382 for (auto focuser : m_Focusers)
383 focuser->setMountCoords(position, pierSide, ha);
384}
385
386FocusState FocusModule::status(const QString &trainname)
387{
388 int id = findFocuser(trainname, true);
389 if (0 <= id && id < m_Focusers.count())
390 return m_Focusers[id]->status();
391 else
392 {
393 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
394 return FocusState::FOCUS_IDLE;
395 }
396}
397
398QString FocusModule::camera(const QString &trainname)
399{
400 int id = findFocuser(trainname, true);
401 if (0 <= id && id < m_Focusers.count())
402 return m_Focusers[id]->camera();
403 else
404 {
405 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
406 return "";
407 }
408}
409
410QString FocusModule::focuser(const QString &trainname)
411{
412 int id = findFocuser(trainname, true);
413 if (0 <= id && id < m_Focusers.count())
414 return m_Focusers[id]->focuser();
415 else
416 {
417 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
418 return "";
419 }
420}
421
422bool FocusModule::addTemperatureSource(const QSharedPointer<ISD::GenericDevice> &device)
423{
424 if (device.isNull())
425 return false;
426
427 for (auto &oneSource : m_TemperatureSources)
428 {
429 if (oneSource->getDeviceName() == device->getDeviceName())
430 return false;
431 }
432
433 m_TemperatureSources.append(device);
434
435 // publish new list of temperature sources to all focusers
436 for (auto focuser : m_Focusers)
437 focuser->updateTemperatureSources(m_TemperatureSources);
438
439 return true;
440}
441
442void FocusModule::syncCameraInfo(const char* devicename)
443{
444 // publish the change to all focusers
445 for (auto focuser : m_Focusers)
446 if (focuser->camera() == devicename)
447 focuser->syncCameraInfo();
448}
449
450void FocusModule::clearLog()
451{
452 m_LogText.clear();
453 emit newLog(QString());
454}
455
456void FocusModule::appendLogText(const QString &logtext)
457{
458 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
459 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), logtext));
460
461 qCInfo(KSTARS_EKOS_FOCUS) << logtext;
462
463 emit newLog(logtext);
464}
465
466void FocusModule::appendFocusLogText(const QString &lines)
467{
468 if (Options::focusLogging())
469 {
470
471 if (!m_FocusLogFile.exists())
472 {
473 // Create focus-specific log file and write the header record
474 QDir dir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
475 dir.mkpath("focuslogs");
476 m_FocusLogEnabled = m_FocusLogFile.open(QIODevice::WriteOnly | QIODevice::Text);
477 if (m_FocusLogEnabled)
478 {
479 QTextStream header(&m_FocusLogFile);
480 header << "date, time, position, temperature, filter, HFR, altitude\n";
481 header.flush();
482 }
483 else
484 qCWarning(KSTARS_EKOS_FOCUS) << "Failed to open focus log file: " << m_FocusLogFileName;
485 }
486
487 if (m_FocusLogEnabled)
488 {
489 QTextStream out(&m_FocusLogFile);
490 out << QDateTime::currentDateTime().toString("yyyy-MM-dd, hh:mm:ss, ") << lines;
491 out.flush();
492 }
493 }
494
495}
496
497bool FocusModule::start(const QString &trainname)
498{
499 int id = findFocuser(trainname, true);
500 if (0 <= id && id < m_Focusers.count())
501 {
502 m_Focusers[id]->start();
503 return true;
504 }
505 else
506 {
507 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
508 return false;
509 }
510}
511
512bool FocusModule::capture(const QString &trainname, double settleTime)
513{
514 int id = findFocuser(trainname, true);
515 if (0 <= id && id < m_Focusers.count())
516 {
517 m_Focusers[id]->capture(settleTime);
518 return true;
519 }
520 else
521 {
522 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
523 return false;
524 }
525}
526
527bool FocusModule::focusIn(const QString &trainname, int ms)
528{
529 int id = findFocuser(trainname, true);
530 if (0 <= id && id < m_Focusers.count())
531 return m_Focusers[id]->focusIn(ms);
532 else
533 {
534 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
535 return false;
536 }
537}
538
539bool FocusModule::focusOut(const QString &trainname, int ms)
540{
541 int id = findFocuser(trainname, true);
542 if (0 <= id && id < m_Focusers.count())
543 return m_Focusers[id]->focusOut(ms);
544 else
545 {
546 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << id;
547 return false;
548 }
549
550}
551
552void FocusModule::removeDevice(const QSharedPointer<ISD::GenericDevice> &deviceRemoved)
553{
554 // Check in Temperature Sources.
555 for (auto &oneSource : m_TemperatureSources)
556 if (oneSource->getDeviceName() == deviceRemoved->getDeviceName())
557 m_TemperatureSources.removeAll(oneSource);
558
559 // publish the change to all focusers
560 for (auto focuser : m_Focusers)
561 focuser->removeDevice(deviceRemoved);
562}
563
564
565void FocusModule::initFocuser(QSharedPointer<Focus> newFocuser)
566{
567 connect(newFocuser.get(), &Focus::focuserChanged, this, &FocusModule::updateFocuser);
568 connect(newFocuser.get(), &Focus::suspendGuiding, this, &FocusModule::suspendGuiding);
569 connect(newFocuser.get(), &Focus::resumeGuiding, this, &FocusModule::resumeGuiding);
570 connect(newFocuser.get(), &Focus::resumeGuiding, this, &FocusModule::resumeGuiding);
571 connect(newFocuser.get(), &Focus::newStatus, this, &FocusModule::newStatus);
572 connect(newFocuser.get(), &Focus::focusAdaptiveComplete, this, &FocusModule::focusAdaptiveComplete);
573 connect(newFocuser.get(), &Focus::newHFR, this, &FocusModule::newHFR);
574 connect(newFocuser.get(), &Focus::newFocusTemperatureDelta, this, &FocusModule::newFocusTemperatureDelta);
575 connect(newFocuser.get(), &Focus::inSequenceAF, this, &FocusModule::inSequenceAF);
576 connect(newFocuser.get(), &Focus::newLog, this, &FocusModule::appendLogText);
577 connect(newFocuser.get(), &Focus::newFocusLog, this, &FocusModule::appendFocusLogText);
578}
579
580QSharedPointer<Focus> FocusModule::addFocuser(const QString &trainname)
581{
582 QSharedPointer<Focus> newFocuser;
583 newFocuser.reset(new Focus(m_Focusers.count()));
584
585 // create the new tab and bring it to front
586 const int tabIndex = focusTabs->insertTab(std::max(0, focusTabs->count() - 1), newFocuser.get(), "new Focuser");
587 focusTabs->setCurrentIndex(tabIndex);
588 // make the tab first tab non closeable
589 if (tabIndex == 0)
590 focusTabs->tabBar()->setTabButton(0, QTabBar::RightSide, nullptr);
591
592 // find an unused train for additional tabs
593 const QString train = tabIndex == 0 ? "" : findUnusedOpticalTrain();
594
595 m_Focusers.append(newFocuser);
596 // select an unused train
597 if (train != "")
598 newFocuser->opticalTrainCombo->setCurrentText(train);
599
600 // set the weather sources
601 newFocuser->updateTemperatureSources(m_TemperatureSources);
602 // set the optical train
603 if (trainname != "" && newFocuser->opticalTrainCombo->findText(trainname))
604 newFocuser->opticalTrainCombo->setCurrentText(trainname);
605
606 // update the tab text
607 updateFocuser(tabIndex, true);
608 initFocuser(newFocuser);
609
610 return newFocuser;
611}
612
613void FocusModule::updateFocuser(int tabID, bool isValid)
614{
615 if (isValid)
616 {
617 if (tabID < focusTabs->count() && tabID < m_Focusers.count() && !m_Focusers[tabID].isNull())
618 {
619 const QString name = m_Focusers[tabID]->m_Focuser != nullptr ?
620 m_Focusers[tabID]->m_Focuser->getDeviceName() :
621 "no focuser";
622 focusTabs->setTabText(tabID, name);
623 }
624 else
625 qCWarning(KSTARS_EKOS_FOCUS) << "Unknown focuser ID:" << tabID;
626 }
627 else
628 focusTabs->setTabText(focusTabs->currentIndex(), "no focuser");
629}
630
631void FocusModule::closeFocuserTab(int tabIndex)
632{
633 // ignore close event from the "Add" tab
634 if (tabIndex == focusTabs->count() - 1)
635 return;
636
637 focusTabs->removeTab(tabIndex);
638 // select the next one on the left
639 focusTabs->setCurrentIndex(std::max(0, tabIndex - 1));
640 // clear the focuser
641 auto focuser = m_Focusers.at(tabIndex);
642 focuser->disconnect(this);
643 focuser->disconnectSyncSettings();
644 m_Focusers.removeAt(tabIndex);
645}
646
647void FocusModule::showOptions()
648{
649 int tabID = focusTabs->currentIndex();
650 KConfigDialog * focusSettings = KConfigDialog::exists(m_Focusers[tabID]->opsDialogName());
651 if (focusSettings)
652 {
653 focusSettings->show();
654 focusSettings->raise();
655 }
656}
657
658void FocusModule::checkCloseFocuserTab(int tabIndex)
659{
660 if (m_Focusers[tabIndex]->isBusy())
661 {
662 // if accept has been clicked, abort and close the tab
663 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, &tabIndex]()
664 {
665 KSMessageBox::Instance()->disconnect(this);
666 m_Focusers[tabIndex]->abort();
667 closeFocuserTab(tabIndex);
668 });
669 // if cancel has been clicked, do not close the tab
670 connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [this]()
671 {
672 KSMessageBox::Instance()->disconnect(this);
673 });
674
675 KSMessageBox::Instance()->warningContinueCancel(i18n("Camera %1 is busy. Abort to close?",
676 m_Focusers[tabIndex]->m_Focuser->getDeviceName()), i18n("Stop capturing"), 30, false, i18n("Abort"));
677 }
678 else
679 {
680 closeFocuserTab(tabIndex);
681
682 }
683}
684
685const QString FocusModule::findUnusedOpticalTrain()
686{
687 QList<QString> names = OpticalTrainManager::Instance()->getTrainNames();
688 foreach(auto focuser, m_Focusers)
689 names.removeAll(focuser->opticalTrain());
690
691 if (names.isEmpty())
692 return "";
693 else
694 return names.first();
695}
696
697}
static KConfigDialog * exists(const QString &name)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
void clicked(bool checked)
void setIcon(const QIcon &icon)
QDateTime currentDateTime()
QString toString(QStringView format, QCalendar cal) const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
void accepted()
void rejected()
QIcon fromTheme(const QString &name)
T & first()
bool isEmpty() const const
qsizetype removeAll(const AT &t)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T * get() const const
bool isNull() const const
void tabCloseRequested(int index)
QWidget(QWidget *parent, Qt::WindowFlags f)
void setFixedSize(const QSize &s)
void setupUi(QWidget *widget)
void setToolTip(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:56:00 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.