diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp index afaa005c1..07fc6de49 100644 --- a/kstars/ekos/capture/capture.cpp +++ b/kstars/ekos/capture/capture.cpp @@ -1,5858 +1,5887 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "capture.h" #include "captureadaptor.h" #include "dslrinfodialog.h" #include "kstars.h" #include "kstarsdata.h" #include "Options.h" #include "rotatorsettings.h" #include "sequencejob.h" #include "skymap.h" #include "ui_calibrationoptions.h" #include "auxiliary/QProgressIndicator.h" #include "ekos/manager.h" #include "ekos/auxiliary/darklibrary.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "indi/driverinfo.h" #include "indi/indifilter.h" #include "indi/clientmanager.h" #include "oal/observeradd.h" #include #include #define MF_TIMER_TIMEOUT 90000 #define GD_TIMER_TIMEOUT 60000 #define MF_RA_DIFF_LIMIT 4 // Wait 3-minutes as maximum beyond exposure // value. #define CAPTURE_TIMEOUT_THRESHOLD 180000 // Current Sequence File Format: #define SQ_FORMAT_VERSION 2.0 // We accept file formats with version back to: #define SQ_COMPAT_VERSION 2.0 namespace Ekos { Capture::Capture() { setupUi(this); qRegisterMetaType("Ekos::CaptureState"); qDBusRegisterMetaType(); new CaptureAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Capture", this); QPointer ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", - QDBusConnection::sessionBus(), this); + QDBusConnection::sessionBus(), this); // Connecting DBus signals connect(ekosInterface, SIGNAL(newModule(QString)), this, SLOT(registerNewModule(QString))); // ensure that the mount interface is present registerNewModule("Mount"); KStarsData::Instance()->userdb()->GetAllDSLRInfos(DSLRInfos); dirPath = QUrl::fromLocalFile(QDir::homePath()); //isAutoGuiding = false; rotatorSettings.reset(new RotatorSettings(this)); pi = new QProgressIndicator(this); progressLayout->addWidget(pi, 0, 4, 1, 1); seqFileCount = 0; //seqWatcher = new KDirWatch(); seqTimer = new QTimer(this); connect(seqTimer, &QTimer::timeout, this, &Ekos::Capture::captureImage); connect(startB, &QPushButton::clicked, this, &Ekos::Capture::toggleSequence); connect(pauseB, &QPushButton::clicked, this, &Ekos::Capture::pause); startB->setIcon(QIcon::fromTheme("media-playback-start")); startB->setAttribute(Qt::WA_LayoutUsesWidgetRect); pauseB->setIcon(QIcon::fromTheme("media-playback-pause")); pauseB->setAttribute(Qt::WA_LayoutUsesWidgetRect); filterManagerB->setIcon(QIcon::fromTheme("view-filter")); filterManagerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); FilterDevicesCombo->addItem("--"); connect(binXIN, static_cast(&QSpinBox::valueChanged), binYIN, &QSpinBox::setValue); - connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Capture::setDefaultCCD); + connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Capture::setDefaultCCD); connect(CCDCaptureCombo, static_cast(&QComboBox::activated), this, &Ekos::Capture::checkCCD); connect(liveVideoB, &QPushButton::clicked, this, &Ekos::Capture::toggleVideo); guideDeviationTimer.setInterval(GD_TIMER_TIMEOUT); connect(&guideDeviationTimer, &QTimer::timeout, this, &Ekos::Capture::checkGuideDeviationTimeout); connect(clearConfigurationB, &QPushButton::clicked, this, &Ekos::Capture::clearCameraConfiguration); connect(FilterDevicesCombo, static_cast(&QComboBox::activated), this, &Ekos::Capture::checkFilter); connect(temperatureCheck, &QCheckBox::toggled, [this](bool toggled) { if (currentCCD) { QVariantMap auxInfo = currentCCD->getDriverInfo()->getAuxInfo(); auxInfo[QString("%1_TC").arg(currentCCD->getDeviceName())] = toggled; currentCCD->getDriverInfo()->setAuxInfo(auxInfo); } }); connect(FilterPosCombo, static_cast(&QComboBox::currentIndexChanged), [=]() { updateHFRThreshold(); }); connect(previewB, &QPushButton::clicked, this, &Ekos::Capture::captureOne); //connect( seqWatcher, SIGNAL(dirty(QString)), this, &Ekos::Capture::checkSeqFile(QString))); connect(addToQueueB, &QPushButton::clicked, this, &Ekos::Capture::addJob); connect(removeFromQueueB, &QPushButton::clicked, this, &Ekos::Capture::removeJob); connect(queueUpB, &QPushButton::clicked, this, &Ekos::Capture::moveJobUp); connect(queueDownB, &QPushButton::clicked, this, &Ekos::Capture::moveJobDown); connect(selectFITSDirB, &QPushButton::clicked, this, &Ekos::Capture::saveFITSDirectory); connect(queueSaveB, &QPushButton::clicked, this, static_cast(&Ekos::Capture::saveSequenceQueue)); connect(queueSaveAsB, &QPushButton::clicked, this, &Ekos::Capture::saveSequenceQueueAs); connect(queueLoadB, &QPushButton::clicked, this, static_cast(&Ekos::Capture::loadSequenceQueue)); connect(resetB, &QPushButton::clicked, this, &Ekos::Capture::resetJobs); connect(queueTable, &QAbstractItemView::doubleClicked, this, &Ekos::Capture::editJob); connect(queueTable, &QTableWidget::itemSelectionChanged, this, &Ekos::Capture::resetJobEdit); - connect(setTemperatureB, &QPushButton::clicked, [&]() { + connect(setTemperatureB, &QPushButton::clicked, [&]() + { if (currentCCD) currentCCD->setTemperature(temperatureIN->value()); }); - connect(coolerOnB, &QPushButton::clicked, [&]() { + connect(coolerOnB, &QPushButton::clicked, [&]() + { if (currentCCD) currentCCD->setCoolerControl(true); }); - connect(coolerOffB, &QPushButton::clicked, [&]() { + connect(coolerOffB, &QPushButton::clicked, [&]() + { if (currentCCD) currentCCD->setCoolerControl(false); }); connect(temperatureIN, &QDoubleSpinBox::editingFinished, setTemperatureB, static_cast(&QPushButton::setFocus)); connect(frameTypeCombo, static_cast(&QComboBox::activated), this, &Ekos::Capture::checkFrameType); connect(resetFrameB, &QPushButton::clicked, this, &Ekos::Capture::resetFrame); connect(calibrationB, &QPushButton::clicked, this, &Ekos::Capture::openCalibrationDialog); connect(rotatorB, &QPushButton::clicked, rotatorSettings.get(), &Ekos::Capture::show); addToQueueB->setIcon(QIcon::fromTheme("list-add")); addToQueueB->setAttribute(Qt::WA_LayoutUsesWidgetRect); removeFromQueueB->setIcon(QIcon::fromTheme("list-remove")); removeFromQueueB->setAttribute(Qt::WA_LayoutUsesWidgetRect); queueUpB->setIcon(QIcon::fromTheme("go-up")); queueUpB->setAttribute(Qt::WA_LayoutUsesWidgetRect); queueDownB->setIcon(QIcon::fromTheme("go-down")); queueDownB->setAttribute(Qt::WA_LayoutUsesWidgetRect); selectFITSDirB->setIcon( - QIcon::fromTheme("document-open-folder")); + QIcon::fromTheme("document-open-folder")); selectFITSDirB->setAttribute(Qt::WA_LayoutUsesWidgetRect); queueLoadB->setIcon(QIcon::fromTheme("document-open")); queueLoadB->setAttribute(Qt::WA_LayoutUsesWidgetRect); queueSaveB->setIcon(QIcon::fromTheme("document-save")); queueSaveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); queueSaveAsB->setIcon(QIcon::fromTheme("document-save-as")); queueSaveAsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); resetB->setIcon(QIcon::fromTheme("system-reboot")); resetB->setAttribute(Qt::WA_LayoutUsesWidgetRect); resetFrameB->setIcon(QIcon::fromTheme("view-refresh")); resetFrameB->setAttribute(Qt::WA_LayoutUsesWidgetRect); calibrationB->setIcon(QIcon::fromTheme("run-build")); calibrationB->setAttribute(Qt::WA_LayoutUsesWidgetRect); rotatorB->setIcon(QIcon::fromTheme("kstars_solarsystem")); rotatorB->setAttribute(Qt::WA_LayoutUsesWidgetRect); addToQueueB->setToolTip(i18n("Add job to sequence queue")); removeFromQueueB->setToolTip(i18n("Remove job from sequence queue")); fitsDir->setText(Options::fitsDir()); for (auto &filter : FITSViewer::filterTypes) filterCombo->addItem(filter); guideDeviationCheck->setChecked(Options::enforceGuideDeviation()); guideDeviation->setValue(Options::guideDeviation()); autofocusCheck->setChecked(Options::enforceAutofocus()); refocusEveryNCheck->setChecked(Options::enforceRefocusEveryN()); meridianCheck->setChecked(Options::autoMeridianFlip()); meridianHours->setValue(Options::autoMeridianHours()); - QCheckBox * const checkBoxes[] = { + QCheckBox * const checkBoxes[] = + { guideDeviationCheck, refocusEveryNCheck, guideDeviationCheck, meridianCheck }; - for (const QCheckBox* control : checkBoxes) + for (const QCheckBox * control : checkBoxes) connect(control, &QCheckBox::toggled, this, &Ekos::Capture::setDirty); - QDoubleSpinBox *const dspinBoxes[] { + QDoubleSpinBox * const dspinBoxes[] + { HFRPixels, guideDeviation, meridianHours }; - for (const QDoubleSpinBox *control : dspinBoxes) - connect(control, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Capture::setDirty); + for (const QDoubleSpinBox * control : dspinBoxes) + connect(control, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Capture::setDirty); connect(uploadModeCombo, static_cast(&QComboBox::activated), this, &Ekos::Capture::setDirty); connect(remoteDirIN, &QLineEdit::editingFinished, this, &Ekos::Capture::setDirty); m_ObserverName = Options::defaultObserver(); observerB->setIcon(QIcon::fromTheme("im-user")); observerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(observerB, &QPushButton::clicked, this, &Ekos::Capture::showObserverDialog); // Exposure Timeout captureTimeout.setSingleShot(true); connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Capture::processCaptureTimeout); // Post capture script connect(&postCaptureScript, static_cast(&QProcess::finished), this, &Ekos::Capture::postScriptFinished); // Remote directory connect(uploadModeCombo, static_cast(&QComboBox::activated), this, - [&](int index) { remoteDirIN->setEnabled(index != 0); }); + [&](int index) + { + remoteDirIN->setEnabled(index != 0); + }); customPropertiesDialog.reset(new CustomProperties()); - connect(customValuesB, &QPushButton::clicked, [&]() { + connect(customValuesB, &QPushButton::clicked, [&]() + { customPropertiesDialog.get()->show(); customPropertiesDialog.get()->raise(); }); flatFieldSource = static_cast(Options::calibrationFlatSourceIndex()); flatFieldDuration = static_cast(Options::calibrationFlatDurationIndex()); wallCoord.setAz(Options::calibrationWallAz()); wallCoord.setAlt(Options::calibrationWallAlt()); targetADU = Options::calibrationADUValue(); targetADUTolerance = Options::calibrationADUValueTolerance(); fitsDir->setText(Options::captureDirectory()); - connect(fitsDir, &QLineEdit::textChanged, [&]() { Options::setCaptureDirectory(fitsDir->text());}); + connect(fitsDir, &QLineEdit::textChanged, [&]() + { + Options::setCaptureDirectory(fitsDir->text()); + }); if (Options::remoteCaptureDirectory().isEmpty() == false) { remoteDirIN->setText(Options::remoteCaptureDirectory()); } - connect(remoteDirIN, &QLineEdit::editingFinished, [&]() { Options::setRemoteCaptureDirectory(remoteDirIN->text());}); + connect(remoteDirIN, &QLineEdit::editingFinished, [&]() + { + Options::setRemoteCaptureDirectory(remoteDirIN->text()); + }); // Keep track of TARGET transfer format when changing CCDs (FITS or NATIVE). Actual format is not changed until capture connect( - transferFormatCombo, static_cast(&QComboBox::activated), this, - [&](int index) + transferFormatCombo, static_cast(&QComboBox::activated), this, + [&](int index) { if (currentCCD) currentCCD->setTargetTransferFormat(static_cast(index)); Options::setCaptureFormatIndex(index); }); // Load FIlter Offets //loadFilterOffsets(); //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); } Capture::~Capture() { qDeleteAll(jobs); } void Capture::setDefaultCCD(QString ccd) { Options::setDefaultCaptureCCD(ccd); } -void Capture::addCCD(ISD::GDInterface *newCCD) +void Capture::addCCD(ISD::GDInterface * newCCD) { - ISD::CCD *ccd = static_cast(newCCD); + ISD::CCD * ccd = static_cast(newCCD); if (CCDs.contains(ccd)) return; CCDs.append(ccd); CCDCaptureCombo->addItem(ccd->getDeviceName()); if (Filters.count() > 0) syncFilterInfo(); checkCCD(); } -void Capture::addGuideHead(ISD::GDInterface *newCCD) +void Capture::addGuideHead(ISD::GDInterface * newCCD) { QString guiderName = newCCD->getDeviceName() + QString(" Guider"); if (CCDCaptureCombo->findText(guiderName) == -1) { CCDCaptureCombo->addItem(guiderName); CCDs.append(static_cast(newCCD)); } } -void Capture::addFilter(ISD::GDInterface *newFilter) +void Capture::addFilter(ISD::GDInterface * newFilter) { - foreach (ISD::GDInterface *filter, Filters) + foreach (ISD::GDInterface * filter, Filters) { if (!strcmp(filter->getDeviceName(), newFilter->getDeviceName())) return; } FilterDevicesCombo->addItem(newFilter->getDeviceName()); Filters.append(static_cast(newFilter)); filterManagerB->setEnabled(true); checkFilter(1); FilterDevicesCombo->setCurrentIndex(1); } void Capture::pause() { pauseFunction = nullptr; m_State = CAPTURE_PAUSED; emit newStatus(Ekos::CAPTURE_PAUSED); appendLogText(i18n("Sequence shall be paused after current exposure is complete.")); pauseB->setEnabled(false); startB->setIcon(QIcon::fromTheme("media-playback-start")); startB->setToolTip(i18n("Resume Sequence")); } void Capture::toggleSequence() { if (m_State == CAPTURE_PAUSED) { startB->setIcon( - QIcon::fromTheme("media-playback-stop")); + QIcon::fromTheme("media-playback-stop")); startB->setToolTip(i18n("Stop Sequence")); pauseB->setEnabled(true); m_State = CAPTURE_CAPTURING; emit newStatus(Ekos::CAPTURE_CAPTURING); appendLogText(i18n("Sequence resumed.")); // Call from where ever we have left of when we paused if (pauseFunction) (this->*pauseFunction)(); } else if (m_State == CAPTURE_IDLE || m_State == CAPTURE_ABORTED || m_State == CAPTURE_COMPLETE) { start(); } else { abort(); } } void Capture::registerNewModule(const QString &name) { if (name == "Mount") { delete mountInterface; mountInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos/Mount", "org.kde.kstars.Ekos.Mount", QDBusConnection::sessionBus(), this); } } void Capture::start() { if (darkSubCheck->isChecked()) { KMessageBox::error(this, i18n("Auto dark subtract is not supported in batch mode.")); return; } Options::setGuideDeviation(guideDeviation->value()); Options::setEnforceGuideDeviation(guideDeviationCheck->isChecked()); Options::setEnforceAutofocus(autofocusCheck->isChecked()); Options::setEnforceRefocusEveryN(refocusEveryNCheck->isChecked()); Options::setAutoMeridianFlip(meridianCheck->isChecked()); Options::setAutoMeridianHours(meridianHours->value()); // Reset progress option if there is no captured frame map set at the time of start - fixes the end-user setting the option just before starting ignoreJobProgress = !capturedFramesMap.count() && Options::alwaysResetSequenceWhenStarting(); if (queueTable->rowCount() == 0) { if (addJob() == false) return; } - SequenceJob *first_job = nullptr; + SequenceJob * first_job = nullptr; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { if (job->getStatus() == SequenceJob::JOB_IDLE || job->getStatus() == SequenceJob::JOB_ABORTED) { first_job = job; break; } } // If there are no idle nor aborted jobs, question is whether to reset and restart // Scheduler will start a non-empty new job each time and doesn't use this execution path if (first_job == nullptr) { // If we have at least one job that are in error, bail out, even if ignoring job progress - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { if (job->getStatus() != SequenceJob::JOB_DONE) { appendLogText(i18n("No pending jobs found. Please add a job to the sequence queue.")); return; } } // If we only have completed jobs and we don't ignore job progress, ask the end-user what to do if (!ignoreJobProgress) if(KMessageBox::warningContinueCancel( nullptr, i18n("All jobs are complete. Do you want to reset the status of all jobs and restart capturing?"), i18n("Reset job status"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "reset_job_complete_status_warning") != KMessageBox::Continue) return; // If the end-user accepted to reset, reset all jobs and restart - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) job->resetStatus(); first_job = jobs.first(); } // If we need to ignore job progress, systematically reset all jobs and restart // Scheduler will never ignore job progress and doesn't use this path else if (ignoreJobProgress) { appendLogText(i18n("Warning: option \"Always Reset Sequence When Starting\" is enabled and resets the sequence counts.")); - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) job->resetStatus(); } // Refocus timer should not be reset on deviation error if (m_DeviationDetected == false && m_State != CAPTURE_SUSPENDED) { meridianFlipStage = MF_NONE; // start timer to measure time until next forced refocus startRefocusEveryNTimer(); } // Only reset these counters if we are NOT restarting from deviation errors // So when starting a new job or fresh then we reset them. if (m_DeviationDetected == false) { ditherCounter = Options::ditherFrames(); inSequenceFocusCounter = Options::inSequenceCheckFrames(); } m_DeviationDetected = false; m_SpikeDetected = false; m_State = CAPTURE_PROGRESS; emit newStatus(Ekos::CAPTURE_PROGRESS); startB->setIcon(QIcon::fromTheme("media-playback-stop")); startB->setToolTip(i18n("Stop Sequence")); pauseB->setEnabled(true); setBusy(true); if (guideDeviationCheck->isChecked() && autoGuideReady == false) appendLogText(i18n("Warning: Guide deviation is selected but autoguide process was not started.")); if (autofocusCheck->isChecked() && m_AutoFocusReady == false) appendLogText(i18n("Warning: in-sequence focusing is selected but autofocus process was not started.")); prepareJob(first_job); } void Capture::stop(CaptureState targetState) { retries = 0; //seqTotalCount = 0; //seqCurrentCount = 0; captureTimeout.stop(); ADURaw.clear(); ExpRaw.clear(); if (activeJob) { if (activeJob->getStatus() == SequenceJob::JOB_BUSY) { QString stopText; switch (targetState) { case CAPTURE_IDLE: stopText = i18n("CCD capture stopped"); - break; + break; case CAPTURE_SUSPENDED: - stopText = i18n("CCD capture suspended"); - break; + stopText = i18n("CCD capture suspended"); + break; - default: - stopText = i18n("CCD capture aborted"); - break; + default: + stopText = i18n("CCD capture aborted"); + break; } KSNotification::event(QLatin1String("CaptureFailed"), stopText); appendLogText(stopText); activeJob->abort(); if (activeJob->isPreview() == false) { int index = jobs.indexOf(activeJob); QJsonObject oneSequence = m_SequenceArray[index].toObject(); oneSequence["Status"] = "Aborted"; m_SequenceArray.replace(index, oneSequence); emit sequenceChanged(m_SequenceArray); } emit newStatus(targetState); } // In case of batch job if (activeJob->isPreview() == false) { activeJob->disconnect(this); activeJob->reset(); } // or preview job in calibration stage else if (calibrationStage == CAL_CALIBRATION) { activeJob->disconnect(this); activeJob->reset(); activeJob->setPreview(false); currentCCD->setUploadMode(rememberUploadMode); } // or regular preview job else { currentCCD->setUploadMode(rememberUploadMode); jobs.removeOne(activeJob); // Delete preview job delete (activeJob); activeJob = nullptr; } } calibrationStage = CAL_NONE; m_State = targetState; // Turn off any calibration light, IF they were turned on by Capture module if (dustCap && dustCapLightEnabled) { dustCapLightEnabled = false; dustCap->SetLightEnabled(false); } if (lightBox && lightBoxLightEnabled) { lightBoxLightEnabled = false; lightBox->SetLightEnabled(false); } secondsLabel->clear(); disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS); disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Capture::setExposureProgress); disconnect(currentCCD, &ISD::CCD::ready, this, &Ekos::Capture::ready); currentCCD->setFITSDir(""); // In case of exposure looping, let's abort if (currentCCD->isLooping()) targetChip->abortExposure(); imgProgress->reset(); imgProgress->setEnabled(false); fullImgCountOUT->setText(QString()); currentImgCountOUT->setText(QString()); exposeOUT->setText(QString()); setBusy(false); if (m_State == CAPTURE_ABORTED || m_State == CAPTURE_SUSPENDED) { startB->setIcon( - QIcon::fromTheme("media-playback-start")); + QIcon::fromTheme("media-playback-start")); startB->setToolTip(i18n("Start Sequence")); pauseB->setEnabled(false); } //foreach (QAbstractButton *button, queueEditButtonGroup->buttons()) //button->setEnabled(true); seqTimer->stop(); activeJob = nullptr; } -void Capture::sendNewImage(const QString &filename, ISD::CCDChip *myChip) +void Capture::sendNewImage(const QString &filename, ISD::CCDChip * myChip) { if (activeJob && (myChip == nullptr || myChip == targetChip)) { + activeJob->setProperty("filename", filename); emit newImage(activeJob); // We only emit this for client/both images since remote images already send this automatically if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL && activeJob->isPreview() == false) emit newSequenceImage(filename); } } bool Capture::setCamera(const QString &device) { for (int i = 0; i < CCDCaptureCombo->count(); i++) if (device == CCDCaptureCombo->itemText(i)) { CCDCaptureCombo->setCurrentIndex(i); checkCCD(i); return true; } return false; } QString Capture::camera() { - if (currentCCD) - return currentCCD->getDeviceName(); + if (currentCCD) + return currentCCD->getDeviceName(); - return QString(); + return QString(); } void Capture::checkCCD(int ccdNum) { if (ccdNum == -1) { ccdNum = CCDCaptureCombo->currentIndex(); if (ccdNum == -1) return; } if (ccdNum <= CCDs.count()) { // Check whether main camera or guide head only currentCCD = CCDs.at(ccdNum); if (CCDCaptureCombo->itemText(ccdNum).right(6) == QString("Guider")) { useGuideHead = true; targetChip = currentCCD->getChip(ISD::CCDChip::GUIDE_CCD); } else { currentCCD = CCDs.at(ccdNum); targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); useGuideHead = false; } // Do not change any settings if we are capturing. if (targetChip && targetChip->isCapturing()) return; - foreach (ISD::CCD *ccd, CCDs) + foreach (ISD::CCD * ccd, CCDs) { disconnect(ccd, &ISD::CCD::numberUpdated, this, &Ekos::Capture::processCCDNumber); disconnect(ccd, &ISD::CCD::newTemperatureValue, this, &Ekos::Capture::updateCCDTemperature); disconnect(ccd, &ISD::CCD::coolerToggled, this, &Ekos::Capture::setCoolerToggled); disconnect(ccd, &ISD::CCD::newRemoteFile, this, &Ekos::Capture::setNewRemoteFile); disconnect(ccd, &ISD::CCD::videoStreamToggled, this, &Ekos::Capture::setVideoStreamEnabled); disconnect(ccd, &ISD::CCD::ready, this, &Ekos::Capture::ready); } if (currentCCD->hasCoolerControl()) { coolerOnB->setEnabled(true); coolerOffB->setEnabled(true); coolerOnB->setChecked(currentCCD->isCoolerOn()); coolerOffB->setChecked(!currentCCD->isCoolerOn()); } else { coolerOnB->setEnabled(false); coolerOnB->setChecked(false); coolerOffB->setEnabled(false); coolerOffB->setChecked(false); } if (currentCCD->hasCooler()) { temperatureCheck->setEnabled(true); temperatureIN->setEnabled(true); if (currentCCD->getBaseDevice()->getPropertyPermission("CCD_TEMPERATURE") != IP_RO) { double min, max, step; setTemperatureB->setEnabled(true); temperatureIN->setReadOnly(false); temperatureCheck->setEnabled(true); currentCCD->getMinMaxStep("CCD_TEMPERATURE", "CCD_TEMPERATURE_VALUE", &min, &max, &step); temperatureIN->setMinimum(min); temperatureIN->setMaximum(max); temperatureIN->setSingleStep(1); bool isChecked = currentCCD->getDriverInfo()->getAuxInfo().value(QString("%1_TC").arg(currentCCD->getDeviceName()), false).toBool(); temperatureCheck->setChecked(isChecked); } else { setTemperatureB->setEnabled(false); temperatureIN->setReadOnly(true); temperatureCheck->setEnabled(false); temperatureCheck->setChecked(false); } double temperature = 0; if (currentCCD->getTemperature(&temperature)) { temperatureOUT->setText(QString("%L1").arg(temperature, 0, 'f', 2)); if (temperatureIN->cleanText().isEmpty()) temperatureIN->setValue(temperature); } } else { temperatureCheck->setEnabled(false); temperatureIN->setEnabled(false); temperatureIN->clear(); temperatureOUT->clear(); setTemperatureB->setEnabled(false); } updateFrameProperties(); QStringList frameTypes = targetChip->getFrameTypes(); frameTypeCombo->clear(); if (frameTypes.isEmpty()) frameTypeCombo->setEnabled(false); else { frameTypeCombo->setEnabled(true); frameTypeCombo->addItems(frameTypes); frameTypeCombo->setCurrentIndex(targetChip->getFrameType()); } QStringList isoList = targetChip->getISOList(); ISOCombo->clear(); transferFormatCombo->blockSignals(true); transferFormatCombo->clear(); if (isoList.isEmpty()) { ISOCombo->setEnabled(false); ISOLabel->setEnabled(false); // Only one transfer format transferFormatCombo->addItem(i18n("FITS")); } else { ISOCombo->setEnabled(true); ISOLabel->setEnabled(true); ISOCombo->addItems(isoList); ISOCombo->setCurrentIndex(targetChip->getISOIndex()); // DSLRs have two transfer formats transferFormatCombo->addItem(i18n("FITS")); transferFormatCombo->addItem(i18n("Native")); //transferFormatCombo->setCurrentIndex(currentCCD->getTargetTransferFormat()); // 2018-05-07 JM: Set value to the value in options transferFormatCombo->setCurrentIndex(Options::captureFormatIndex()); double pixelX = 0, pixelY = 0; bool rc = targetChip->getPixelSize(pixelX, pixelY); bool isModelInDB = isModelinDSLRInfo(QString(currentCCD->getDeviceName())); // If rc == true, then the property has been defined by the driver already // Only then we check if the pixels are zero if (rc == true && (pixelX == 0 || pixelY == 0 || isModelInDB == false)) { // If model is already in database, no need to show dialog // The zeros above are the initial packets so we can safely ignore them if (isModelInDB == false) { DSLRInfo infoDialog(this, currentCCD); if (infoDialog.exec() == QDialog::Accepted) { addDSLRInfo(QString(currentCCD->getDeviceName()), infoDialog.sensorMaxWidth, infoDialog.sensorMaxHeight, infoDialog.sensorPixelW, infoDialog.sensorPixelH); } } } } transferFormatCombo->blockSignals(false); customPropertiesDialog->setCCD(currentCCD); liveVideoB->setEnabled(currentCCD->hasVideoStream()); if (currentCCD->hasVideoStream()) setVideoStreamEnabled(currentCCD->isStreamingEnabled()); else liveVideoB->setIcon(QIcon::fromTheme("camera-off")); connect(currentCCD, &ISD::CCD::numberUpdated, this, &Ekos::Capture::processCCDNumber, Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::newTemperatureValue, this, &Ekos::Capture::updateCCDTemperature, Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::coolerToggled, this, &Ekos::Capture::setCoolerToggled, Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::newRemoteFile, this, &Ekos::Capture::setNewRemoteFile); connect(currentCCD, &ISD::CCD::videoStreamToggled, this, &Ekos::Capture::setVideoStreamEnabled); connect(currentCCD, &ISD::CCD::ready, this, &Ekos::Capture::ready); } } -void Capture::setGuideChip(ISD::CCDChip *chip) +void Capture::setGuideChip(ISD::CCDChip * chip) { guideChip = chip; // We should suspend guide in two scenarios: // 1. If guide chip is within the primary CCD, then we cannot download any data from guide chip while primary CCD is downloading. // 2. If we have two CCDs running from ONE driver (Multiple-Devices-Per-Driver mpdp is true). Same issue as above, only one download // at a time. // After primary CCD download is complete, we resume guiding. suspendGuideOnDownload = - (currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) || - (guideChip->getCCD() == currentCCD && currentCCD->getDriverInfo()->getAuxInfo().value("mdpd", false).toBool()); + (currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) || + (guideChip->getCCD() == currentCCD && currentCCD->getDriverInfo()->getAuxInfo().value("mdpd", false).toBool()); } void Capture::resetFrameToZero() { frameXIN->setMinimum(0); frameXIN->setMaximum(0); frameXIN->setValue(0); frameYIN->setMinimum(0); frameYIN->setMaximum(0); frameYIN->setValue(0); frameWIN->setMinimum(0); frameWIN->setMaximum(0); frameWIN->setValue(0); frameHIN->setMinimum(0); frameHIN->setMaximum(0); frameHIN->setValue(0); } void Capture::updateFrameProperties(int reset) { int binx = 1, biny = 1; double min, max, step; int xstep = 0, ystep = 0; QString frameProp = useGuideHead ? QString("GUIDER_FRAME") : QString("CCD_FRAME"); QString exposureProp = useGuideHead ? QString("GUIDER_EXPOSURE") : QString("CCD_EXPOSURE"); QString exposureElem = useGuideHead ? QString("GUIDER_EXPOSURE_VALUE") : QString("CCD_EXPOSURE_VALUE"); targetChip = - useGuideHead ? currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) : currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); + useGuideHead ? currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) : currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); frameWIN->setEnabled(targetChip->canSubframe()); frameHIN->setEnabled(targetChip->canSubframe()); frameXIN->setEnabled(targetChip->canSubframe()); frameYIN->setEnabled(targetChip->canSubframe()); binXIN->setEnabled(targetChip->canBin()); binYIN->setEnabled(targetChip->canBin()); QList exposureValues; exposureValues << 0.01 << 0.02 << 0.05 << 0.1 << 0.2 << 0.25 << 0.5 << 1 << 1.5 << 2 << 2.5 << 3 << 5 << 6 << 7 << 8 << 9 << 10 << 20 << 30 << 40 << 50 << 60 << 120 << 180 << 300 << 600 << 900 << 1200 << 1800; if (currentCCD->getMinMaxStep(exposureProp, exposureElem, &min, &max, &step)) { if (min < 0.001) exposureIN->setDecimals(6); else exposureIN->setDecimals(3); for(int i = 0; i < exposureValues.count(); i++) { double value = exposureValues.at(i); if(value < min || value > max) { exposureValues.removeAt(i); i--; //So we don't skip one } } exposureValues.prepend(min); exposureValues.append(max); } exposureIN->setRecommendedValues(exposureValues); if (currentCCD->getMinMaxStep(frameProp, "WIDTH", &min, &max, &step)) { if (min >= max) { resetFrameToZero(); return; } if (step == 0) xstep = static_cast(max * 0.05); else xstep = step; if (min >= 0 && max > 0) { frameWIN->setMinimum(min); frameWIN->setMaximum(max); frameWIN->setSingleStep(xstep); } } else return; if (currentCCD->getMinMaxStep(frameProp, "HEIGHT", &min, &max, &step)) { if (min >= max) { resetFrameToZero(); return; } if (step == 0) ystep = static_cast(max * 0.05); else ystep = step; if (min >= 0 && max > 0) { frameHIN->setMinimum(min); frameHIN->setMaximum(max); frameHIN->setSingleStep(ystep); } } else return; if (currentCCD->getMinMaxStep(frameProp, "X", &min, &max, &step)) { if (min >= max) { resetFrameToZero(); return; } if (step == 0) step = xstep; if (min >= 0 && max > 0) { frameXIN->setMinimum(min); frameXIN->setMaximum(max); frameXIN->setSingleStep(step); } } else return; if (currentCCD->getMinMaxStep(frameProp, "Y", &min, &max, &step)) { if (min >= max) { resetFrameToZero(); return; } if (step == 0) step = ystep; if (min >= 0 && max > 0) { frameYIN->setMinimum(min); frameYIN->setMaximum(max); frameYIN->setSingleStep(step); } } else return; // cull to camera limits, if there are any if (useGuideHead == false) cullToDSLRLimits(); if (reset == 1 || frameSettings.contains(targetChip) == false) { QVariantMap settings; settings["x"] = 0; settings["y"] = 0; settings["w"] = frameWIN->maximum(); settings["h"] = frameHIN->maximum(); settings["binx"] = 1; settings["biny"] = 1; frameSettings[targetChip] = settings; } else if (reset == 2 && frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; int x, y, w, h; x = settings["x"].toInt(); y = settings["y"].toInt(); w = settings["w"].toInt(); h = settings["h"].toInt(); // Bound them x = qBound(frameXIN->minimum(), x, frameXIN->maximum() - 1); y = qBound(frameYIN->minimum(), y, frameYIN->maximum() - 1); w = qBound(frameWIN->minimum(), w, frameWIN->maximum()); h = qBound(frameHIN->minimum(), h, frameHIN->maximum()); settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; frameSettings[targetChip] = settings; } if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; int x = settings["x"].toInt(); int y = settings["y"].toInt(); int w = settings["w"].toInt(); int h = settings["h"].toInt(); if (targetChip->canBin()) { targetChip->getMaxBin(&binx, &biny); binXIN->setMaximum(binx); binYIN->setMaximum(biny); binXIN->setValue(settings["binx"].toInt()); binYIN->setValue(settings["biny"].toInt()); } else { binXIN->setValue(1); binYIN->setValue(1); } if (x >= 0) frameXIN->setValue(x); if (y >= 0) frameYIN->setValue(y); if (w > 0) frameWIN->setValue(w); if (h > 0) frameHIN->setValue(h); } } -void Capture::processCCDNumber(INumberVectorProperty *nvp) +void Capture::processCCDNumber(INumberVectorProperty * nvp) { if (currentCCD == nullptr) return; if ((!strcmp(nvp->name, "CCD_FRAME") && useGuideHead == false) || (!strcmp(nvp->name, "GUIDER_FRAME") && useGuideHead)) updateFrameProperties(); else if ((!strcmp(nvp->name, "CCD_INFO") && useGuideHead == false) || (!strcmp(nvp->name, "GUIDER_INFO") && useGuideHead)) updateFrameProperties(2); } void Capture::resetFrame() { targetChip = - useGuideHead ? currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) : currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); + useGuideHead ? currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) : currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); targetChip->resetFrame(); updateFrameProperties(1); } -void Capture::syncFrameType(ISD::GDInterface *ccd) +void Capture::syncFrameType(ISD::GDInterface * ccd) { if (strcmp(ccd->getDeviceName(), CCDCaptureCombo->currentText().toLatin1())) return; - ISD::CCDChip *tChip = (static_cast(ccd))->getChip(ISD::CCDChip::PRIMARY_CCD); + ISD::CCDChip * tChip = (static_cast(ccd))->getChip(ISD::CCDChip::PRIMARY_CCD); QStringList frameTypes = tChip->getFrameTypes(); frameTypeCombo->clear(); if (frameTypes.isEmpty()) frameTypeCombo->setEnabled(false); else { frameTypeCombo->setEnabled(true); frameTypeCombo->addItems(frameTypes); frameTypeCombo->setCurrentIndex(tChip->getFrameType()); } } bool Capture::setFilterWheel(const QString &device) { bool deviceFound = false; for (int i = 1; i < FilterDevicesCombo->count(); i++) if (device == FilterDevicesCombo->itemText(i)) { checkFilter(i); deviceFound = true; break; } if (deviceFound == false) return false; return true; } QString Capture::filterWheel() { if (FilterDevicesCombo->currentIndex() >= 1) return FilterDevicesCombo->currentText(); return QString(); } bool Capture::setFilter(const QString &filter) { if (FilterDevicesCombo->currentIndex() >= 1) { FilterPosCombo->setCurrentText(filter); return true; } return false; } QString Capture::filter() { return FilterPosCombo->currentText(); } void Capture::checkFilter(int filterNum) { if (filterNum == -1) { filterNum = FilterDevicesCombo->currentIndex(); if (filterNum == -1) return; } // "--" is no filter if (filterNum == 0) { currentFilter = nullptr; m_CurrentFilterPosition=-1; FilterPosCombo->clear(); syncFilterInfo(); return; } if (filterNum <= Filters.count()) currentFilter = Filters.at(filterNum-1); filterManager->setCurrentFilterWheel(currentFilter); syncFilterInfo(); FilterPosCombo->clear(); FilterPosCombo->addItems(filterManager->getFilterLabels()); m_CurrentFilterPosition = filterManager->getFilterPosition(); FilterPosCombo->setCurrentIndex(m_CurrentFilterPosition-1); /*if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || activeJob->getStatus() == SequenceJob::JOB_IDLE)) activeJob->setCurrentFilter(currentFilterPosition);*/ } void Capture::syncFilterInfo() { if (currentCCD) { - ITextVectorProperty *activeDevices = currentCCD->getBaseDevice()->getText("ACTIVE_DEVICES"); + ITextVectorProperty * activeDevices = currentCCD->getBaseDevice()->getText("ACTIVE_DEVICES"); if (activeDevices) { - IText *activeFilter = IUFindText(activeDevices, "ACTIVE_FILTER"); + IText * activeFilter = IUFindText(activeDevices, "ACTIVE_FILTER"); if (activeFilter) { if (currentFilter != nullptr && strcmp(activeFilter->text, currentFilter->getDeviceName())) { IUSaveText(activeFilter, currentFilter->getDeviceName()); currentCCD->getDriverInfo()->getClientManager()->sendNewText(activeDevices); } // Reset filter name in CCD driver else if (currentFilter == nullptr && strlen(activeFilter->text) > 0) { IUSaveText(activeFilter, ""); currentCCD->getDriverInfo()->getClientManager()->sendNewText(activeDevices); } } } } } bool Capture::startNextExposure() { if (m_State == CAPTURE_PAUSED) { pauseFunction = &Capture::startNextExposure; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); return false; } if (seqDelay > 0) { secondsLabel->setText(i18n("Waiting...")); m_State = CAPTURE_WAITING; emit newStatus(Ekos::CAPTURE_WAITING); } seqTimer->start(seqDelay); return true; } -void Capture::newFITS(IBLOB *bp) +void Capture::newFITS(IBLOB * bp) { - ISD::CCDChip *tChip = nullptr; + ISD::CCDChip * tChip = nullptr; // If there is no active job, ignore if (activeJob == nullptr || meridianFlipStage >= MF_ALIGNING) return; if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) { if (bp == nullptr) { appendLogText(i18n("Failed to save file to %1", activeJob->getSignature())); abort(); return; } if (!strcmp(bp->name, "CCD2")) tChip = currentCCD->getChip(ISD::CCDChip::GUIDE_CCD); else tChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); if (tChip != targetChip) return; if (targetChip->getCaptureMode() == FITS_FOCUS || targetChip->getCaptureMode() == FITS_GUIDE) return; // If this is a preview job, make sure to enable preview button after // we receive the FITS if (activeJob->isPreview() && previewB->isEnabled() == false) previewB->setEnabled(true); // If the FITS is not for our device, simply ignore //if (QString(bp->bvp->device) != currentCCD->getDeviceName() || (startB->isEnabled() && previewB->isEnabled())) if (QString(bp->bvp->device) != currentCCD->getDeviceName() || m_State == CAPTURE_IDLE || m_State == CAPTURE_ABORTED) return; if (currentCCD->isLooping() == false) { disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS); if (useGuideHead == false && darkSubCheck->isChecked() && activeJob->isPreview()) { - FITSView *currentImage = targetChip->getImageView(FITS_NORMAL); - FITSData *darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, activeJob->getExposure()); + FITSView * currentImage = targetChip->getImageView(FITS_NORMAL); + FITSData * darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, activeJob->getExposure()); uint16_t offsetX = activeJob->getSubX() / activeJob->getXBin(); uint16_t offsetY = activeJob->getSubY() / activeJob->getYBin(); connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, &Ekos::Capture::setCaptureComplete); connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Capture::appendLogText); if (darkData) DarkLibrary::Instance()->subtract(darkData, currentImage, activeJob->getCaptureFilter(), offsetX, offsetY); else DarkLibrary::Instance()->captureAndSubtract(targetChip, currentImage, activeJob->getExposure(), offsetX, offsetY); return; } } } - blobChip = bp ? static_cast(bp->aux0) : nullptr; + blobChip = bp ? static_cast(bp->aux0) : nullptr; blobFilename= bp ? static_cast(bp->aux2) : QString(); setCaptureComplete(); } bool Capture::setCaptureComplete() { captureTimeout.stop(); captureTimeoutCounter = 0; if (currentCCD->isLooping() == false) { disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Capture::setExposureProgress); DarkLibrary::Instance()->disconnect(this); } secondsLabel->setText(i18n("Complete.")); // Do not display notifications for very short captures if (activeJob->getExposure() >= 1) KSNotification::event(QLatin1String("EkosCaptureImageReceived"), i18n("Captured image received"), KSNotification::EVENT_INFO); // If it was initially set as pure preview job and NOT as preview for calibration if (activeJob->isPreview() && calibrationStage != CAL_CALIBRATION) { sendNewImage(blobFilename, blobChip); jobs.removeOne(activeJob); // Reset upload mode if it was changed by preview currentCCD->setUploadMode(rememberUploadMode); delete (activeJob); // Reset active job pointer activeJob = nullptr; abort(); if (guideState == GUIDE_SUSPENDED && suspendGuideOnDownload) emit resumeGuiding(); m_State = CAPTURE_IDLE; emit newStatus(Ekos::CAPTURE_IDLE); return true; } if (m_State == CAPTURE_PAUSED) { pauseFunction = &Capture::setCaptureComplete; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); return false; } /* Increase the sequence's current capture count */ if (! activeJob->isPreview()) activeJob->setCompleted(activeJob->getCompleted() + 1); sendNewImage(blobFilename, blobChip); /* If we were assigned a captured frame map, also increase the relevant counter for prepareJob */ SchedulerJob::CapturedFramesMap::iterator frame_item = capturedFramesMap.find(activeJob->getSignature()); if (capturedFramesMap.end() != frame_item) frame_item.value()++; if (activeJob->getFrameType() != FRAME_LIGHT) { if (processPostCaptureCalibrationStage() == false) return true; if (calibrationStage == CAL_CALIBRATION_COMPLETE) calibrationStage = CAL_CAPTURING; } /* The image progress has now one more capture */ imgProgress->setValue(activeJob->getCompleted()); appendLogText(i18n("Received image %1 out of %2.", activeJob->getCompleted(), activeJob->getCount())); m_State = CAPTURE_IMAGE_RECEIVED; emit newStatus(Ekos::CAPTURE_IMAGE_RECEIVED); currentImgCountOUT->setText(QString("%L1").arg(activeJob->getCompleted())); // Check if we need to execute post capture script first if (activeJob->getPostCaptureScript().isEmpty() == false) { postCaptureScript.start(activeJob->getPostCaptureScript()); appendLogText(i18n("Executing post capture script %1", activeJob->getPostCaptureScript())); return true; } // if we're done if (activeJob->getCount() <= activeJob->getCompleted()) { processJobCompletion(); return true; } // Check if meridian condition is met if (checkMeridianFlip()) return true; return resumeSequence(); } void Capture::processJobCompletion() { activeJob->done(); if (activeJob->isPreview() == false) { int index = jobs.indexOf(activeJob); QJsonObject oneSequence = m_SequenceArray[index].toObject(); oneSequence["Status"] = "Complete"; m_SequenceArray.replace(index, oneSequence); emit sequenceChanged(m_SequenceArray); } stop(); // Check if meridian condition is met IF there are more pending jobs in the queue // Otherwise, no need to check meridian flip is all jobs are over. if (getPendingJobCount() > 0 && checkMeridianFlip()) return; // Check if there are more pending jobs and execute them if (resumeSequence()) return; // Otherwise, we're done. We park if required and resume guiding if no parking is done and autoguiding was engaged before. else { //KNotification::event(QLatin1String("CaptureSuccessful"), i18n("CCD capture sequence completed")); KSNotification::event(QLatin1String("CaptureSuccessful"), i18n("CCD capture sequence completed"), KSNotification::EVENT_INFO); abort(); m_State = CAPTURE_COMPLETE; emit newStatus(Ekos::CAPTURE_COMPLETE); //Resume guiding if it was suspended before //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) if (guideState == GUIDE_SUSPENDED && suspendGuideOnDownload) emit resumeGuiding(); } } bool Capture::resumeSequence() { if (m_State == CAPTURE_PAUSED) { pauseFunction = &Capture::resumeSequence; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); return false; } // If no job is active, we have to find if there are more pending jobs in the queue if (!activeJob) { - SequenceJob *next_job = nullptr; + SequenceJob * next_job = nullptr; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { if (job->getStatus() == SequenceJob::JOB_IDLE || job->getStatus() == SequenceJob::JOB_ABORTED) { next_job = job; break; } } if (next_job) { prepareJob(next_job); //Resume guiding if it was suspended before //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) if (guideState == GUIDE_SUSPENDED && suspendGuideOnDownload) { qCDebug(KSTARS_EKOS_CAPTURE) << "Resuming guiding..."; emit resumeGuiding(); } return true; } else { qCDebug(KSTARS_EKOS_CAPTURE) << "All capture jobs complete."; return false; } } // Otherwise, let's prepare for next exposure after making sure in-sequence focus and dithering are complete if applicable. else { isInSequenceFocus = (m_AutoFocusReady && autofocusCheck->isChecked()/* && HFRPixels->value() > 0*/); // if (isInSequenceFocus) // requiredAutoFocusStarted = false; // Reset HFR pixels to file value after meridian flip if (isInSequenceFocus && meridianFlipStage != MF_NONE) { qCDebug(KSTARS_EKOS_CAPTURE) << "Resetting HFR value to file value of" << fileHFR << "pixels after meridian flip."; //firstAutoFocus = true; HFRPixels->setValue(fileHFR); } // If we suspended guiding due to primary chip download, resume guide chip guiding now if (guideState == GUIDE_SUSPENDED && suspendGuideOnDownload) { qCInfo(KSTARS_EKOS_CAPTURE) << "Resuming guiding..."; emit resumeGuiding(); } // Dither either when guiding or IF Non-Guide either option is enabled if ( Options::ditherEnabled() - // 2017-09-20 Jasem: No need to dither after post meridian flip guiding - && meridianFlipStage != MF_GUIDING - // If CCD is looping, we cannot dither UNLESS a different camera and NOT a guide chip is doing the guiding for us. - && (currentCCD->isLooping() == false || guideChip == nullptr) - // We must be either in guide mode or if non-guide dither (via pulsing) is enabled - && (guideState == GUIDE_GUIDING || Options::ditherNoGuiding()) - // Must be only done for light frames - && activeJob->getFrameType() == FRAME_LIGHT - // Check dither counter - && --ditherCounter == 0) + // 2017-09-20 Jasem: No need to dither after post meridian flip guiding + && meridianFlipStage != MF_GUIDING + // If CCD is looping, we cannot dither UNLESS a different camera and NOT a guide chip is doing the guiding for us. + && (currentCCD->isLooping() == false || guideChip == nullptr) + // We must be either in guide mode or if non-guide dither (via pulsing) is enabled + && (guideState == GUIDE_GUIDING || Options::ditherNoGuiding()) + // Must be only done for light frames + && activeJob->getFrameType() == FRAME_LIGHT + // Check dither counter + && --ditherCounter == 0) { ditherCounter = Options::ditherFrames(); secondsLabel->setText(i18n("Dithering...")); qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering..."; if (currentCCD->isLooping()) targetChip->abortExposure(); m_State = CAPTURE_DITHERING; emit newStatus(Ekos::CAPTURE_DITHERING); } #if 0 else if (isRefocus && activeJob->getFrameType() == FRAME_LIGHT) { appendLogText(i18n("Scheduled refocus starting after %1 seconds...",getRefocusEveryNTimerElapsedSec())); secondsLabel->setText(i18n("Focusing...")); if (currentCCD->isLooping()) targetChip->abortExposure(); // If we are over 30 mins since last autofocus, we'll reset frame. if (refocusEveryN->value() >= 30) emit resetFocus(); // force refocus emit checkFocus(0.1); m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); } else if (isInSequenceFocus && activeJob->getFrameType() == FRAME_LIGHT && --inSequenceFocusCounter == 0) { inSequenceFocusCounter = Options::inSequenceCheckFrames(); // Post meridian flip we need to reset filter _before_ running in-sequence focusing // as it could have changed for whatever reason (e.g. alignment used a different filter). // Then when focus process begins with the _target_ filter in place, it should take all the necessary actions to make it // work for the next set of captures. This is direct reset to the filter device, not via Filter Manager. if (meridianFlipStage != MF_NONE && currentFilter) { int targetFilterPosition = activeJob->getTargetFilter(); int currentFilterPosition= filterManager->getFilterPosition(); if (targetFilterPosition > 0 && targetFilterPosition != currentFilterPosition) currentFilter->runCommand(INDI_SET_FILTER, &targetFilterPosition); } secondsLabel->setText(i18n("Focusing...")); if (currentCCD->isLooping()) targetChip->abortExposure(); if (HFRPixels->value() == 0) emit checkFocus(0.1); else emit checkFocus(HFRPixels->value()); qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started..."; m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); } #endif // Check if we need to do autofocus, if not let's check if we need looping or start next exposure else if (startFocusIfRequired() == false) { // If looping, we just increment the file system image count if (currentCCD->isLooping()) { if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) { checkSeqBoundary(activeJob->getSignature()); currentCCD->setNextSequenceID(nextSequenceID); } } else startNextExposure(); } } return true; } bool Capture::startFocusIfRequired() { if (activeJob->getFrameType() != FRAME_LIGHT) return false; // if (autoFocusReady == false) // return false; // check if time for forced refocus if (refocusEveryNCheck->isChecked()) { qCDebug(KSTARS_EKOS_CAPTURE) << "NFocus Elapsed Time (secs): " << getRefocusEveryNTimerElapsedSec() << " Requested Interval (secs): " << refocusEveryN->value()*60; isRefocus = getRefocusEveryNTimerElapsedSec() >= refocusEveryN->value()*60; } else isRefocus = false; if (isRefocus) { appendLogText(i18n("Scheduled refocus starting after %1 seconds...",getRefocusEveryNTimerElapsedSec())); secondsLabel->setText(i18n("Focusing...")); if (currentCCD->isLooping()) targetChip->abortExposure(); // If we are over 30 mins since last autofocus, we'll reset frame. if (refocusEveryN->value() >= 30) emit resetFocus(); // force refocus emit checkFocus(0.1); m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); return true; } else if (isInSequenceFocus && --inSequenceFocusCounter == 0) { inSequenceFocusCounter = Options::inSequenceCheckFrames(); // Post meridian flip we need to reset filter _before_ running in-sequence focusing // as it could have changed for whatever reason (e.g. alignment used a different filter). // Then when focus process begins with the _target_ filter in place, it should take all the necessary actions to make it // work for the next set of captures. This is direct reset to the filter device, not via Filter Manager. if (meridianFlipStage != MF_NONE && currentFilter) { int targetFilterPosition = activeJob->getTargetFilter(); int currentFilterPosition= filterManager->getFilterPosition(); if (targetFilterPosition > 0 && targetFilterPosition != currentFilterPosition) currentFilter->runCommand(INDI_SET_FILTER, &targetFilterPosition); } secondsLabel->setText(i18n("Focusing...")); if (currentCCD->isLooping()) targetChip->abortExposure(); if (HFRPixels->value() == 0) emit checkFocus(0.1); else emit checkFocus(HFRPixels->value()); qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started..."; m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); return true; } return false; } void Capture::captureOne() { //if (currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) /*if (uploadModeCombo->currentIndex() != ISD::CCD::UPLOAD_CLIENT) { appendLogText(i18n("Cannot take preview image while CCD upload mode is set to local or both. Please change " "upload mode to client and try again.")); return; }*/ if (transferFormatCombo->currentIndex() == ISD::CCD::FORMAT_NATIVE && darkSubCheck->isChecked()) { appendLogText(i18n("Cannot perform auto dark subtraction of native DSLR formats.")); return; } if (addJob(true)) prepareJob(jobs.last()); } void Capture::captureImage() { if (activeJob == nullptr) return; captureTimeout.stop(); seqTimer->stop(); SequenceJob::CAPTUREResult rc = SequenceJob::CAPTURE_OK; if (currentCCD->isConnected() == false) { appendLogText(i18n("Error: Lost connection to CCD.")); abort(); return; } if (focusState >= FOCUS_PROGRESS) { appendLogText(i18n("Cannot capture while focus module is busy.")); abort(); return; } /* if (filterSlot != nullptr) { currentFilterPosition = (int)filterSlot->np[0].value; activeJob->setCurrentFilter(currentFilterPosition); }*/ if (currentFilter != nullptr) { m_CurrentFilterPosition = filterManager->getFilterPosition(); activeJob->setCurrentFilter(m_CurrentFilterPosition); } if (currentCCD->hasCooler()) { double temperature = 0; currentCCD->getTemperature(&temperature); activeJob->setCurrentTemperature(temperature); } if (currentCCD->isLooping()) { int remaining = activeJob->getCount() - activeJob->getCompleted(); if (remaining > 1) currentCCD->setExposureLoopCount(remaining); } connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS, Qt::UniqueConnection); if (activeJob->getFrameType() == FRAME_FLAT) { // If we have to calibrate ADU levels, first capture must be preview and not in batch mode if (activeJob->isPreview() == false && activeJob->getFlatFieldDuration() == DURATION_ADU && calibrationStage == CAL_PRECAPTURE_COMPLETE) { if (currentCCD->getTransferFormat() == ISD::CCD::FORMAT_NATIVE) { appendLogText(i18n("Cannot calculate ADU levels in non-FITS images.")); abort(); return; } calibrationStage = CAL_CALIBRATION; // We need to be in preview mode and in client mode for this to work activeJob->setPreview(true); } } // Temporary change upload mode to client when requesting previews if (activeJob->isPreview()) { rememberUploadMode = activeJob->getUploadMode(); currentCCD->setUploadMode(ISD::CCD::UPLOAD_CLIENT); } if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) { checkSeqBoundary(activeJob->getSignature()); currentCCD->setNextSequenceID(nextSequenceID); } m_State = CAPTURE_CAPTURING; //if (activeJob->isPreview() == false) // NOTE: Why we didn't emit this before for preview? emit newStatus(Ekos::CAPTURE_CAPTURING); if (frameSettings.contains(activeJob->getActiveChip())) { QVariantMap settings; settings["x"] = activeJob->getSubX(); settings["y"] = activeJob->getSubY(); settings["w"] = activeJob->getSubW(); settings["h"] = activeJob->getSubH(); settings["binx"] = activeJob->getXBin(); settings["biny"] = activeJob->getYBin(); frameSettings[activeJob->getActiveChip()] = settings; } // If using DSLR, make sure it is set to correct transfer format currentCCD->setTransformFormat(activeJob->getTransforFormat()); connect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Capture::setExposureProgress, Qt::UniqueConnection); rc = activeJob->capture(darkSubCheck->isChecked() ? true : false); if (rc != SequenceJob::CAPTURE_OK) { disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Capture::setExposureProgress); } switch (rc) { - case SequenceJob::CAPTURE_OK: - { - appendLogText(i18n("Capturing %1-second %2 image...", QString("%L1").arg(activeJob->getExposure(), 0, 'f', 3), activeJob->getFilterName())); - captureTimeout.start(activeJob->getExposure() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); - if (activeJob->isPreview() == false) + case SequenceJob::CAPTURE_OK: { - int index = jobs.indexOf(activeJob); - QJsonObject oneSequence = m_SequenceArray[index].toObject(); - oneSequence["Status"] = "In Progress"; - m_SequenceArray.replace(index, oneSequence); - emit sequenceChanged(m_SequenceArray); + appendLogText(i18n("Capturing %1-second %2 image...", QString("%L1").arg(activeJob->getExposure(), 0, 'f', 3), activeJob->getFilterName())); + captureTimeout.start(activeJob->getExposure() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); + if (activeJob->isPreview() == false) + { + int index = jobs.indexOf(activeJob); + QJsonObject oneSequence = m_SequenceArray[index].toObject(); + oneSequence["Status"] = "In Progress"; + m_SequenceArray.replace(index, oneSequence); + emit sequenceChanged(m_SequenceArray); + } } - } break; - case SequenceJob::CAPTURE_FRAME_ERROR: - appendLogText(i18n("Failed to set sub frame.")); - abort(); - break; + case SequenceJob::CAPTURE_FRAME_ERROR: + appendLogText(i18n("Failed to set sub frame.")); + abort(); + break; - case SequenceJob::CAPTURE_BIN_ERROR: - appendLogText(i18n("Failed to set binning.")); - abort(); - break; + case SequenceJob::CAPTURE_BIN_ERROR: + appendLogText(i18n("Failed to set binning.")); + abort(); + break; - case SequenceJob::CAPTURE_FILTER_BUSY: - // Try again in 1 second if filter is busy - QTimer::singleShot(1000, this, &Ekos::Capture::captureImage); - break; + case SequenceJob::CAPTURE_FILTER_BUSY: + // Try again in 1 second if filter is busy + QTimer::singleShot(1000, this, &Ekos::Capture::captureImage); + break; - case SequenceJob::CAPTURE_FOCUS_ERROR: - appendLogText(i18n("Cannot capture while focus module is busy.")); - abort(); - break; + case SequenceJob::CAPTURE_FOCUS_ERROR: + appendLogText(i18n("Cannot capture while focus module is busy.")); + abort(); + break; } } bool Capture::resumeCapture() { if (m_State == CAPTURE_PAUSED) { pauseFunction = &Capture::resumeCapture; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); return false; } #if 0 /* Refresh isRefocus when resuming */ if (autoFocusReady && refocusEveryNCheck->isChecked()) { qCDebug(KSTARS_EKOS_CAPTURE) << "NFocus Elapsed Time (secs): " << getRefocusEveryNTimerElapsedSec() << " Requested Interval (secs): " << refocusEveryN->value()*60; isRefocus = getRefocusEveryNTimerElapsedSec() >= refocusEveryN->value()*60; } // FIXME ought to be able to combine these - only different is value passed // to checkFocus() // 2018-08-23 Jasem: For now in-sequence-focusing takes precedense. if (isInSequenceFocus && requiredAutoFocusStarted == false) { requiredAutoFocusStarted = true; secondsLabel->setText(i18n("Focusing...")); qCDebug(KSTARS_EKOS_CAPTURE) << "Requesting focusing if HFR >" << HFRPixels->value(); emit checkFocus(HFRPixels->value()); m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); return true; } else if (isRefocus) { appendLogText(i18n("Scheduled refocus started...")); secondsLabel->setText(i18n("Focusing...")); emit checkFocus(0.1); m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); return true; } #endif if (m_State == CAPTURE_DITHERING && m_AutoFocusReady && startFocusIfRequired()) return true; startNextExposure(); return true; } /*******************************************************************************/ /* Update the prefix for the sequence of images to be captured */ /*******************************************************************************/ void Capture::updateSequencePrefix(const QString &newPrefix, const QString &dir) { seqPrefix = newPrefix; // If it doesn't exist, create it QDir().mkpath(dir); nextSequenceID = 1; } /*******************************************************************************/ /* Determine the next file number sequence. That is, if we have file1.png */ /* and file2.png, then the next sequence should be file3.png */ /*******************************************************************************/ void Capture::checkSeqBoundary(const QString &path) { int newFileIndex = -1; QFileInfo const path_info(path); QString const sig_dir(path_info.dir().path()); QString const sig_file(path_info.baseName()); QString tempName; // seqFileCount = 0; // No updates during meridian flip if (meridianFlipStage >= MF_ALIGNING) return; QDirIterator it(sig_dir, QDir::Files); while (it.hasNext()) { tempName = it.next(); QFileInfo info(tempName); //tempName = info.baseName(); tempName = info.completeBaseName(); QString finalSeqPrefix = seqPrefix; finalSeqPrefix.remove(SequenceJob::ISOMarker); // find the prefix first if (tempName.startsWith(finalSeqPrefix, Qt::CaseInsensitive) == false) continue; /* Do not change the number of captures. * - If the sequence is required by the end-user, unconditionally run what each sequence item is requiring. * - If the sequence is required by the scheduler, use capturedFramesMap to determine when to stop capturing. */ //seqFileCount++; int lastUnderScoreIndex = tempName.lastIndexOf("_"); if (lastUnderScoreIndex > 0) { bool indexOK = false; newFileIndex = tempName.midRef(lastUnderScoreIndex + 1).toInt(&indexOK); if (indexOK && newFileIndex >= nextSequenceID) nextSequenceID = newFileIndex + 1; } } } void Capture::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", - QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS_CAPTURE) << text; emit newLog(text); } void Capture::clearLog() { m_LogText.clear(); emit newLog(QString()); } -void Capture::setExposureProgress(ISD::CCDChip *tChip, double value, IPState state) +void Capture::setExposureProgress(ISD::CCDChip * tChip, double value, IPState state) { if (targetChip != tChip || targetChip->getCaptureMode() != FITS_NORMAL || meridianFlipStage >= MF_ALIGNING) return; exposeOUT->setText(QString("%L1").arg(value, 0, 'd', 2)); if (activeJob) { activeJob->setExposeLeft(value); emit newExposureProgress(activeJob); } if (activeJob && state == IPS_ALERT) { int retries = activeJob->getCaptureRetires() + 1; activeJob->setCaptureRetires(retries); appendLogText(i18n("Capture failed. Check INDI Control Panel for details.")); if (retries == 3) { abort(); return; } appendLogText(i18n("Restarting capture attempt #%1", retries)); nextSequenceID = 1; captureImage(); return; } //qDebug() << "Exposure with value " << value << "state" << pstateStr(state); if (activeJob != nullptr && state == IPS_OK) { activeJob->setCaptureRetires(0); activeJob->setExposeLeft(0); if (currentCCD && currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) { if (activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY) { newFITS(nullptr); return; } } //if (isAutoGuiding && Options::useEkosGuider() && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) if (guideState == GUIDE_GUIDING && Options::guiderType() == 0 && suspendGuideOnDownload) { qCDebug(KSTARS_EKOS_CAPTURE) << "Autoguiding suspended until primary CCD chip completes downloading..."; emit suspendGuiding(); } secondsLabel->setText(i18n("Downloading...")); //disconnect(currentCCD, &ISD::CCD::newExposureValue(ISD::CCDChip*,double,IPState)), this, &Ekos::Capture::updateCaptureProgress(ISD::CCDChip*,double,IPState))); } // JM: Don't change to i18np, value is DOUBLE, not Integer. else if (value <= 1) secondsLabel->setText(i18n("second left")); else secondsLabel->setText(i18n("seconds left")); } void Capture::updateCCDTemperature(double value) { if (temperatureCheck->isEnabled() == false) { if (currentCCD->getBaseDevice()->getPropertyPermission("CCD_TEMPERATURE") != IP_RO) checkCCD(); } temperatureOUT->setText(QString("%L1").arg(value, 0, 'f', 2)); if (temperatureIN->cleanText().isEmpty()) temperatureIN->setValue(value); //if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || activeJob->getStatus() == SequenceJob::JOB_IDLE)) if (activeJob) activeJob->setCurrentTemperature(value); } -void Capture::updateRotatorNumber(INumberVectorProperty *nvp) +void Capture::updateRotatorNumber(INumberVectorProperty * nvp) { if (!strcmp(nvp->name, "ABS_ROTATOR_ANGLE")) { // Update widget rotator position rotatorSettings->setCurrentAngle(nvp->np[0].value); //if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || activeJob->getStatus() == SequenceJob::JOB_IDLE)) if (activeJob) activeJob->setCurrentRotation(rotatorSettings->getCurrentRotationPA()); } } bool Capture::addJob(bool preview) { if (m_State != CAPTURE_IDLE && m_State != CAPTURE_ABORTED && m_State != CAPTURE_COMPLETE) return false; - SequenceJob *job = nullptr; + SequenceJob * job = nullptr; QString imagePrefix; if (preview == false && darkSubCheck->isChecked()) { KMessageBox::error(this, i18n("Auto dark subtract is not supported in batch mode.")); return false; } if (uploadModeCombo->currentIndex() != ISD::CCD::UPLOAD_CLIENT && remoteDirIN->text().isEmpty()) { KMessageBox::error(this, i18n("You must set remote directory for Local & Both modes.")); return false; } if (uploadModeCombo->currentIndex() != ISD::CCD::UPLOAD_LOCAL && fitsDir->text().isEmpty()) { KMessageBox::error(this, i18n("You must set local directory for Client & Both modes.")); return false; } if (m_JobUnderEdit) job = jobs.at(queueTable->currentRow()); else { job = new SequenceJob(); job->setFilterManager(filterManager); } if (job == nullptr) { qWarning() << "Job is nullptr!" << endl; return false; } if (ISOCombo->isEnabled()) job->setISOIndex(ISOCombo->currentIndex()); job->setTransforFormat(static_cast(transferFormatCombo->currentIndex())); job->setPreview(preview); if (temperatureIN->isEnabled()) { double currentTemperature; currentCCD->getTemperature(¤tTemperature); job->setEnforceTemperature(temperatureCheck->isChecked()); job->setTargetTemperature(temperatureIN->value()); job->setCurrentTemperature(currentTemperature); } job->setCaptureFilter(static_cast(filterCombo->currentIndex())); job->setUploadMode(static_cast(uploadModeCombo->currentIndex())); job->setPostCaptureScript(postCaptureScriptIN->text()); job->setFlatFieldDuration(flatFieldDuration); job->setFlatFieldSource(flatFieldSource); job->setPreMountPark(preMountPark); job->setPreDomePark(preDomePark); job->setWallCoord(wallCoord); job->setTargetADU(targetADU); job->setTargetADUTolerance(targetADUTolerance); imagePrefix = prefixIN->text(); constructPrefix(imagePrefix); job->setPrefixSettings(prefixIN->text(), filterCheck->isChecked(), expDurationCheck->isChecked(), ISOCheck->isChecked()); job->setFrameType(static_cast(frameTypeCombo->currentIndex())); job->setFullPrefix(imagePrefix); //if (filterSlot != nullptr && currentFilter != nullptr) if (FilterPosCombo->currentIndex() != -1 && currentFilter != nullptr) job->setTargetFilter(FilterPosCombo->currentIndex() + 1, FilterPosCombo->currentText()); job->setExposure(exposureIN->value()); job->setCount(countIN->value()); job->setBin(binXIN->value(), binYIN->value()); job->setDelay(delayIN->value() * 1000); /* in ms */ job->setActiveChip(targetChip); job->setActiveCCD(currentCCD); job->setActiveFilter(currentFilter); // Custom Properties job->setCustomProperties(customPropertiesDialog->getCustomProperties()); if (currentRotator && rotatorSettings->isRotationEnforced()) { job->setActiveRotator(currentRotator); job->setTargetRotation(rotatorSettings->getTargetRotationPA()); job->setCurrentRotation(rotatorSettings->getCurrentRotationPA()); } job->setFrame(frameXIN->value(), frameYIN->value(), frameWIN->value(), frameHIN->value()); job->setRemoteDir(remoteDirIN->text()); job->setLocalDir(fitsDir->text()); if (m_JobUnderEdit == false) { // JM 2018-09-24: If this is the first job added // We always ignore job progress by default. if (jobs.isEmpty() && preview == false) ignoreJobProgress = true; jobs.append(job); // Nothing more to do if preview if (preview) return true; } QJsonObject jsonJob = {{"Status", "Idle"}}; QString directoryPostfix; /* FIXME: Refactor directoryPostfix assignment, whose code is duplicated in scheduler.cpp */ if (m_TargetName.isEmpty()) directoryPostfix = QLatin1Literal("/") + frameTypeCombo->currentText(); else directoryPostfix = QLatin1Literal("/") + m_TargetName + QLatin1Literal("/") + frameTypeCombo->currentText(); if ((job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT) && job->getFilterName().isEmpty() == false) directoryPostfix += QLatin1Literal("/") + job->getFilterName(); job->setDirectoryPostfix(directoryPostfix); int currentRow = 0; if (m_JobUnderEdit == false) { currentRow = queueTable->rowCount(); queueTable->insertRow(currentRow); } else currentRow = queueTable->currentRow(); - QTableWidgetItem *status = m_JobUnderEdit ? queueTable->item(currentRow, 0) : new QTableWidgetItem(); + QTableWidgetItem * status = m_JobUnderEdit ? queueTable->item(currentRow, 0) : new QTableWidgetItem(); job->setStatusCell(status); - QTableWidgetItem *filter = m_JobUnderEdit ? queueTable->item(currentRow, 1) : new QTableWidgetItem(); + QTableWidgetItem * filter = m_JobUnderEdit ? queueTable->item(currentRow, 1) : new QTableWidgetItem(); filter->setText("--"); jsonJob.insert("Filter", "--"); /*if (frameTypeCombo->currentText().compare("Bias", Qt::CaseInsensitive) && frameTypeCombo->currentText().compare("Dark", Qt::CaseInsensitive) && FilterPosCombo->count() > 0)*/ if (FilterPosCombo->count() > 0 && (frameTypeCombo->currentIndex() == FRAME_LIGHT || frameTypeCombo->currentIndex() == FRAME_FLAT)) { filter->setText(FilterPosCombo->currentText()); jsonJob.insert("Filter", FilterPosCombo->currentText()); } filter->setTextAlignment(Qt::AlignHCenter); filter->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QTableWidgetItem *type = m_JobUnderEdit ? queueTable->item(currentRow, 2) : new QTableWidgetItem(); + QTableWidgetItem * type = m_JobUnderEdit ? queueTable->item(currentRow, 2) : new QTableWidgetItem(); type->setText(frameTypeCombo->currentText()); type->setTextAlignment(Qt::AlignHCenter); type->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); jsonJob.insert("Type", type->text()); - QTableWidgetItem *bin = m_JobUnderEdit ? queueTable->item(currentRow, 3) : new QTableWidgetItem(); + QTableWidgetItem * bin = m_JobUnderEdit ? queueTable->item(currentRow, 3) : new QTableWidgetItem(); bin->setText(QString("%1x%2").arg(binXIN->value()).arg(binYIN->value())); bin->setTextAlignment(Qt::AlignHCenter); bin->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); jsonJob.insert("Bin", bin->text()); - QTableWidgetItem *exp = m_JobUnderEdit ? queueTable->item(currentRow, 4) : new QTableWidgetItem(); + QTableWidgetItem * exp = m_JobUnderEdit ? queueTable->item(currentRow, 4) : new QTableWidgetItem(); exp->setText(QString("%L1").arg(exposureIN->value(), 0, 'f', exposureIN->decimals())); exp->setTextAlignment(Qt::AlignHCenter); exp->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); jsonJob.insert("Exp", exp->text()); - QTableWidgetItem *iso = m_JobUnderEdit ? queueTable->item(currentRow, 5) : new QTableWidgetItem(); + QTableWidgetItem * iso = m_JobUnderEdit ? queueTable->item(currentRow, 5) : new QTableWidgetItem(); if (ISOCombo->currentIndex() != -1) { iso->setText(ISOCombo->currentText()); jsonJob.insert("ISO", iso->text()); } else { iso->setText("--"); jsonJob.insert("ISO", "--"); } iso->setTextAlignment(Qt::AlignHCenter); iso->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QTableWidgetItem *count = m_JobUnderEdit ? queueTable->item(currentRow, 6) : new QTableWidgetItem(); + QTableWidgetItem * count = m_JobUnderEdit ? queueTable->item(currentRow, 6) : new QTableWidgetItem(); job->setCountCell(count); jsonJob.insert("Count", count->text()); if (m_JobUnderEdit == false) { queueTable->setItem(currentRow, 0, status); queueTable->setItem(currentRow, 1, filter); queueTable->setItem(currentRow, 2, type); queueTable->setItem(currentRow, 3, bin); queueTable->setItem(currentRow, 4, exp); queueTable->setItem(currentRow, 5, iso); queueTable->setItem(currentRow, 6, count); m_SequenceArray.append(jsonJob); emit sequenceChanged(m_SequenceArray); } removeFromQueueB->setEnabled(true); if (queueTable->rowCount() > 0) { queueSaveAsB->setEnabled(true); queueSaveB->setEnabled(true); resetB->setEnabled(true); m_Dirty = true; } if (queueTable->rowCount() > 1) { queueUpB->setEnabled(true); queueDownB->setEnabled(true); } if (m_JobUnderEdit) { m_JobUnderEdit = false; resetJobEdit(); appendLogText(i18n("Job #%1 changes applied.", currentRow + 1)); m_SequenceArray.replace(currentRow, jsonJob); emit sequenceChanged(m_SequenceArray); } return true; } void Capture::removeJob(int index) { if (m_State != CAPTURE_IDLE && m_State != CAPTURE_ABORTED && m_State != CAPTURE_COMPLETE) return; if (m_JobUnderEdit) { resetJobEdit(); return; } int currentRow = queueTable->currentRow(); if (index >= 0) currentRow = index; if (currentRow < 0) { currentRow = queueTable->rowCount() - 1; if (currentRow < 0) return; } queueTable->removeRow(currentRow); m_SequenceArray.removeAt(currentRow); emit sequenceChanged(m_SequenceArray); - SequenceJob *job = jobs.at(currentRow); + SequenceJob * job = jobs.at(currentRow); jobs.removeOne(job); if (job == activeJob) activeJob = nullptr; delete job; if (queueTable->rowCount() == 0) removeFromQueueB->setEnabled(false); if (queueTable->rowCount() == 1) { queueUpB->setEnabled(false); queueDownB->setEnabled(false); } for (int i = 0; i < jobs.count(); i++) jobs.at(i)->setStatusCell(queueTable->item(i, 0)); queueTable->selectRow(queueTable->currentRow()); if (queueTable->rowCount() == 0) { queueSaveAsB->setEnabled(false); queueSaveB->setEnabled(false); resetB->setEnabled(false); } m_Dirty = true; } void Capture::moveJobUp() { int currentRow = queueTable->currentRow(); int columnCount = queueTable->columnCount(); if (currentRow <= 0 || queueTable->rowCount() == 1) return; int destinationRow = currentRow - 1; for (int i = 0; i < columnCount; i++) { - QTableWidgetItem *downItem = queueTable->takeItem(currentRow, i); - QTableWidgetItem *upItem = queueTable->takeItem(destinationRow, i); + QTableWidgetItem * downItem = queueTable->takeItem(currentRow, i); + QTableWidgetItem * upItem = queueTable->takeItem(destinationRow, i); queueTable->setItem(destinationRow, i, downItem); queueTable->setItem(currentRow, i, upItem); } - SequenceJob *job = jobs.takeAt(currentRow); + SequenceJob * job = jobs.takeAt(currentRow); jobs.removeOne(job); jobs.insert(destinationRow, job); QJsonObject currentJob = m_SequenceArray[currentRow].toObject(); m_SequenceArray.replace(currentRow, m_SequenceArray[destinationRow]); m_SequenceArray.replace(destinationRow, currentJob); emit sequenceChanged(m_SequenceArray); queueTable->selectRow(destinationRow); for (int i = 0; i < jobs.count(); i++) jobs.at(i)->setStatusCell(queueTable->item(i, 0)); m_Dirty = true; } void Capture::moveJobDown() { int currentRow = queueTable->currentRow(); int columnCount = queueTable->columnCount(); if (currentRow < 0 || queueTable->rowCount() == 1 || (currentRow + 1) == queueTable->rowCount()) return; int destinationRow = currentRow + 1; for (int i = 0; i < columnCount; i++) { - QTableWidgetItem *downItem = queueTable->takeItem(currentRow, i); - QTableWidgetItem *upItem = queueTable->takeItem(destinationRow, i); + QTableWidgetItem * downItem = queueTable->takeItem(currentRow, i); + QTableWidgetItem * upItem = queueTable->takeItem(destinationRow, i); queueTable->setItem(destinationRow, i, downItem); queueTable->setItem(currentRow, i, upItem); } - SequenceJob *job = jobs.takeAt(currentRow); + SequenceJob * job = jobs.takeAt(currentRow); jobs.removeOne(job); jobs.insert(destinationRow, job); QJsonObject currentJob = m_SequenceArray[currentRow].toObject(); m_SequenceArray.replace(currentRow, m_SequenceArray[destinationRow]); m_SequenceArray.replace(destinationRow, currentJob); emit sequenceChanged(m_SequenceArray); queueTable->selectRow(destinationRow); for (int i = 0; i < jobs.count(); i++) jobs.at(i)->setStatusCell(queueTable->item(i, 0)); m_Dirty = true; } void Capture::setBusy(bool enable) { isBusy = enable; enable ? pi->startAnimation() : pi->stopAnimation(); previewB->setEnabled(!enable); - foreach (QAbstractButton *button, queueEditButtonGroup->buttons()) + foreach (QAbstractButton * button, queueEditButtonGroup->buttons()) button->setEnabled(!enable); } -void Capture::prepareJob(SequenceJob *job) +void Capture::prepareJob(SequenceJob * job) { activeJob = job; qCDebug(KSTARS_EKOS_CAPTURE) << "Preparing capture job" << job->getSignature() << "for execution."; if (activeJob->getActiveCCD() != currentCCD) { setCamera(activeJob->getActiveCCD()->getDeviceName()); } /*if (activeJob->isPreview()) seqTotalCount = -1; else seqTotalCount = activeJob->getCount();*/ seqDelay = activeJob->getDelay(); // seqCurrentCount = activeJob->getCompleted(); if (activeJob->isPreview() == false) { fullImgCountOUT->setText(QString("%L1").arg(activeJob->getCount())); currentImgCountOUT->setText(QString("%L1").arg(activeJob->getCompleted())); // set the progress info imgProgress->setEnabled(true); imgProgress->setMaximum(activeJob->getCount()); imgProgress->setValue(activeJob->getCompleted()); if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) updateSequencePrefix(activeJob->getFullPrefix(), QFileInfo(activeJob->getSignature()).path()); } // We check if the job is already fully or partially complete by checking how many files of its type exist on the file system if (activeJob->isPreview() == false) { // The signature is the unique identification path in the system for a particular job. Format is "///". // If the Scheduler is requesting the Capture tab to process a sequence job, a target name will be inserted after the sequence file storage field (e.g. /path/to/storage/target/Light/...) // If the end-user is requesting the Capture tab to process a sequence job, the sequence file storage will be used as is (e.g. /path/to/storage/Light/...) QString signature = activeJob->getSignature(); // Now check on the file system ALL the files that exist with the above signature // If 29 files exist for example, then nextSequenceID would be the NEXT file number (30) // Therefore, we know how to number the next file. // However, we do not deduce the number of captures to process from this function. checkSeqBoundary(signature); // Captured Frames Map contains a list of signatures:count of _already_ captured files in the file system. // This map is set by the Scheduler in order to complete efficiently the required captures. // When the end-user requests a sequence to be processed, that map is empty. // // Example with a 5xL-5xR-5xG-5xB sequence // // When the end-user loads and runs this sequence, each filter gets to capture 5 frames, then the procedure stops. // When the Scheduler executes a job with this sequence, the procedure depends on what is in the storage. // // Let's consider the Scheduler has 3 instances of this job to run. // // When the first job completes the sequence, there are 20 images in the file system (5 for each filter). // When the second job starts, Scheduler finds those 20 images but requires 20 more images, thus sets the frames map counters to 0 for all LRGB frames. // When the third job starts, Scheduler now has 40 images, but still requires 20 more, thus again sets the frames map counters to 0 for all LRGB frames. // // Now let's consider something went wrong, and the third job was aborted before getting to 60 images, say we have full LRG, but only 1xB. // When Scheduler attempts to run the aborted job again, it will count captures in storage, subtract previous job requirements, and set the frames map counters to 0 for LRG, and 4 for B. // When the sequence runs, the procedure will bypass LRG and proceed to capture 4xB. if (capturedFramesMap.contains(signature)) { // Get the current capture count from the map int count = capturedFramesMap[signature]; // Count how many captures this job has to process, given that previous jobs may have done some work already - foreach (SequenceJob *a_job, jobs) + foreach (SequenceJob * a_job, jobs) if (a_job == activeJob) break; else if (a_job->getSignature() == activeJob->getSignature()) count -= a_job->getCompleted(); // This is the current completion count of the current job activeJob->setCompleted(count); } // JM 2018-09-24: Only set completed jobs to 0 IF the scheduler set captured frames map to begin with // If the map is empty, then no scheduler is used and it should proceed as normal. else if (capturedFramesMap.count() > 0) { // No preliminary information, we reset the job count and run the job unconditionally to clarify the behavior activeJob->setCompleted(0); } // JM 2018-09-24: In case ignoreJobProgress is enabled // We check if this particular job progress ignore flag is set. If not, // then we set it and reset completed to zero. Next time it is evaluated here again // It will maintain its count regardless else if (ignoreJobProgress && activeJob->getJobProgressIgnored() == false) { activeJob->setJobProgressIgnored(true); activeJob->setCompleted(0); } // We cannot rely on sequenceID to give us a count - if we don't ignore job progress, we leave the count as it was originally #if 0 // If we cannot ignore job progress, then we set completed job number according to what // was found on the file system. else if (ignoreJobProgress == false) { int count = nextSequenceID - 1; if (count < activeJob->getCount()) activeJob->setCompleted(count); else activeJob->setCompleted(activeJob->getCount()); } #endif // Check whether active job is complete by comparing required captures to what is already available if (activeJob->getCount() <= activeJob->getCompleted()) { activeJob->setCompleted(activeJob->getCount()); appendLogText(i18n("Job requires %1-second %2 images, has already %3/%4 captures and does not need to run.", - QString("%L1").arg(job->getExposure(), 0, 'f', 3), job->getFilterName(), - activeJob->getCompleted(), activeJob->getCount())); + QString("%L1").arg(job->getExposure(), 0, 'f', 3), job->getFilterName(), + activeJob->getCompleted(), activeJob->getCount())); processJobCompletion(); /* FIXME: find a clearer way to exit here */ return; } else { // There are captures to process currentImgCountOUT->setText(QString("%L1").arg(activeJob->getCompleted())); appendLogText(i18n("Job requires %1-second %2 images, has %3/%4 frames captured and will be processed.", - QString("%L1").arg(job->getExposure(), 0, 'f', 3), job->getFilterName(), - activeJob->getCompleted(), activeJob->getCount())); + QString("%L1").arg(job->getExposure(), 0, 'f', 3), job->getFilterName(), + activeJob->getCompleted(), activeJob->getCount())); // Emit progress update - done a few lines below // emit newImage(nullptr, activeJob); currentCCD->setNextSequenceID(nextSequenceID); } } if (currentCCD->isBLOBEnabled() == false) { // FIXME: Move this warning pop-up elsewhere, it will interfere with automation. if (Options::guiderType() != Ekos::Guide::GUIDE_INTERNAL || KMessageBox::questionYesNo(nullptr, i18n("Image transfer is disabled for this camera. Would you like to enable it?")) == KMessageBox::Yes) { currentCCD->setBLOBEnabled(true); } else { setBusy(false); return; } } // Just notification of active job stating up emit newImage(activeJob); //connect(job, SIGNAL(checkFocus()), this, &Ekos::Capture::startPostFilterAutoFocus())); // Reset calibration stage if (calibrationStage == CAL_CAPTURING) { if (job->getFrameType() != FRAME_LIGHT) calibrationStage = CAL_PRECAPTURE_COMPLETE; else calibrationStage = CAL_NONE; } /* Disable this restriction, let the sequence run even if focus did not run prior to the capture. * Besides, this locks up the Scheduler when the Capture module starts a sequence without any prior focus procedure done. * This is quite an old code block. The message "Manual scheduled" seems to even refer to some manual intervention? * With the new HFR threshold, it might be interesting to prevent the execution because we actually need an HFR value to * begin capturing, but even there, on one hand it makes sense for the end-user to know what HFR to put in the edit box, * and on the other hand the focus procedure will deduce the next HFR automatically. * But in the end, it's not entirely clear what the intent was. Note there is still a warning that a preliminary autofocus * procedure is important to avoid any surprise that could make the whole schedule ineffective. */ #if 0 // If we haven't performed a single autofocus yet, we stop //if (!job->isPreview() && Options::enforceRefocusEveryN() && autoFocusReady && isInSequenceFocus == false && firstAutoFocus == true) if (!job->isPreview() && Options::enforceRefocusEveryN() && autoFocusReady == false && isInSequenceFocus == false) { appendLogText(i18n("Manual scheduled focusing is not supported. Run Autofocus process before trying again.")); abort(); return; } #endif #if 0 if (currentFilterPosition > 0) { // If we haven't performed a single autofocus yet, we stop if (!job->isPreview() && Options::autoFocusOnFilterChange() && (isInSequenceFocus == false && firstAutoFocus == true)) { appendLogText(i18n( "Manual focusing post filter change is not supported. Run Autofocus process before trying again.")); abort(); return; } /* if (currentFilterPosition != activeJob->getTargetFilter() && filterFocusOffsets.empty() == false) { int16_t targetFilterOffset = 0; foreach (FocusOffset *offset, filterFocusOffsets) { if (offset->filter == activeJob->getFilterName()) { targetFilterOffset = offset->offset - lastFilterOffset; lastFilterOffset = offset->offset; break; } } if (targetFilterOffset != 0 && (activeJob->getFrameType() == FRAME_LIGHT || activeJob->getFrameType() == FRAME_FLAT)) { appendLogText(i18n("Adjust focus offset by %1 steps", targetFilterOffset)); secondsLabel->setText(i18n("Adjusting filter offset")); if (activeJob->isPreview() == false) { state = CAPTURE_FILTER_FOCUS; emit newStatus(Ekos::CAPTURE_FILTER_FOCUS); } setBusy(true); emit newFocusOffset(targetFilterOffset); return; } } */ } #endif preparePreCaptureActions(); } void Capture::preparePreCaptureActions() { // Update position if (m_CurrentFilterPosition > 0) activeJob->setCurrentFilter(m_CurrentFilterPosition); // update temperature if (currentCCD->hasCooler() && activeJob->getEnforceTemperature()) { double temperature = 0; currentCCD->getTemperature(&temperature); activeJob->setCurrentTemperature(temperature); } // update rotator angle if (currentRotator != nullptr && activeJob->getTargetRotation() != Ekos::INVALID_VALUE) activeJob->setCurrentRotation(rotatorSettings->getCurrentRotationPA()); setBusy(true); if (activeJob->isPreview()) { startB->setIcon( - QIcon::fromTheme("media-playback-stop")); + QIcon::fromTheme("media-playback-stop")); startB->setToolTip(i18n("Stop")); } connect(activeJob, &SequenceJob::prepareState, this, &Ekos::Capture::updatePrepareState); connect(activeJob, &SequenceJob::prepareComplete, this, &Ekos::Capture::executeJob); activeJob->prepareCapture(); } void Capture::updatePrepareState(Ekos::CaptureState prepareState) { m_State = prepareState; emit newStatus(prepareState); switch (prepareState) { - case CAPTURE_SETTING_TEMPERATURE: - appendLogText(i18n("Setting temperature to %1 C...", activeJob->getTargetTemperature())); - secondsLabel->setText(i18n("Set %1 C...", activeJob->getTargetTemperature())); - break; + case CAPTURE_SETTING_TEMPERATURE: + appendLogText(i18n("Setting temperature to %1 C...", activeJob->getTargetTemperature())); + secondsLabel->setText(i18n("Set %1 C...", activeJob->getTargetTemperature())); + break; - case CAPTURE_SETTING_ROTATOR: - appendLogText(i18n("Setting rotation to %1 degrees E of N...", activeJob->getTargetRotation())); - secondsLabel->setText(i18n("Set Rotator %1...", activeJob->getTargetRotation())); - break; + case CAPTURE_SETTING_ROTATOR: + appendLogText(i18n("Setting rotation to %1 degrees E of N...", activeJob->getTargetRotation())); + secondsLabel->setText(i18n("Set Rotator %1...", activeJob->getTargetRotation())); + break; - default: - break; + default: + break; } } void Capture::executeJob() { activeJob->disconnect(this); QMap FITSHeader; if (m_ObserverName.isEmpty() == false) FITSHeader["FITS_OBSERVER"] = m_ObserverName; if (m_TargetName.isEmpty() == false) FITSHeader["FITS_OBJECT"] = m_TargetName; else if (activeJob->getRawPrefix().isEmpty() == false) { FITSHeader["FITS_OBJECT"] = activeJob->getRawPrefix(); } if (FITSHeader.count() > 0) currentCCD->setFITSHeader(FITSHeader); // Update button status setBusy(true); useGuideHead = (activeJob->getActiveChip()->getType() == ISD::CCDChip::PRIMARY_CCD) ? false : true; syncGUIToJob(activeJob); updatePreCaptureCalibrationStatus(); // Check calibration frame requirements #if 0 if (activeJob->getFrameType() != FRAME_LIGHT && activeJob->isPreview() == false) { updatePreCaptureCalibrationStatus(); return; } captureImage(); #endif } void Capture::updatePreCaptureCalibrationStatus() { // If process was aborted or stopped by the user if (isBusy == false) { appendLogText(i18n("Warning: Calibration process was prematurely terminated.")); return; } IPState rc = processPreCaptureCalibrationStage(); if (rc == IPS_ALERT) return; else if (rc == IPS_BUSY) { secondsLabel->clear(); QTimer::singleShot(1000, this, &Ekos::Capture::updatePreCaptureCalibrationStatus); return; } captureImage(); } void Capture::setGuideDeviation(double delta_ra, double delta_dec) { // if (activeJob == nullptr) // { // if (deviationDetected == false) // return; // // Try to find first job that was aborted due to deviation // for(SequenceJob *job : jobs) // { // if (job->getStatus() == SequenceJob::JOB_ABORTED) // { // activeJob = job; // break; // } // } // if (activeJob == nullptr) // return; // } // If guiding is started after a meridian flip we will start getting guide deviations again // if the guide deviations are within our limits, we resume the sequence if (meridianFlipStage == MF_GUIDING) { double deviation_rms = sqrt(delta_ra * delta_ra + delta_dec * delta_dec); // If the user didn't select any guiding deviation, we fall through // otherwise we can for deviation RMS if (guideDeviationCheck->isChecked() == false || deviation_rms < guideDeviation->value()) { appendLogText(i18n("Post meridian flip calibration completed successfully.")); resumeSequence(); // N.B. Set meridian flip stage AFTER resumeSequence() always meridianFlipStage = MF_NONE; return; } } // We don't enforce limit on previews if (guideDeviationCheck->isChecked() == false || (activeJob && (activeJob->isPreview() || activeJob->getExposeLeft() == 0))) return; double deviation_rms = sqrt(delta_ra * delta_ra + delta_dec * delta_dec); QString deviationText = QString("%1").arg(deviation_rms, 0, 'f', 3); // If we have an active busy job, let's abort it if guiding deviation is exceeded. // And we accounted for the spike if (activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY && activeJob->getFrameType() == FRAME_LIGHT) { if (deviation_rms > guideDeviation->value()) { // Ignore spikes ONCE if (m_SpikeDetected == false) { m_SpikeDetected = true; return; } appendLogText(i18n("Guiding deviation %1 exceeded limit value of %2 arcsecs, " "suspending exposure and waiting for guider up to %3 seconds.", deviationText, guideDeviation->value(), QString("%L1").arg(guideDeviationTimer.interval()/1000.0,0,'f',3))); suspend(); m_SpikeDetected = false; // Check if we need to start meridian flip if (checkMeridianFlip()) return; m_DeviationDetected = true; guideDeviationTimer.start(); } return; } // Find the first aborted job SequenceJob * abortedJob = nullptr; - for(SequenceJob *job : jobs) + for(SequenceJob * job : jobs) { if (job->getStatus() == SequenceJob::JOB_ABORTED) { abortedJob = job; break; } } if (abortedJob && m_DeviationDetected) { if (deviation_rms <= guideDeviation->value()) { guideDeviationTimer.stop(); if (seqDelay == 0) appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, " "resuming exposure.", deviationText, guideDeviation->value())); else appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, " "resuming exposure in %3 seconds.", deviationText, guideDeviation->value(), seqDelay / 1000.0)); QTimer::singleShot(seqDelay, this, &Ekos::Capture::start); return; } else appendLogText(i18n("Guiding deviation %1 is still higher than limit value of %2 arcsecs.", - deviationText, guideDeviation->value())); + deviationText, guideDeviation->value())); } } void Capture::setFocusStatus(FocusState state) { // Do NOT update state if in the process of meridian flip if (meridianFlipStage != MF_NONE) return; focusState = state; if (focusState > FOCUS_ABORTED) return; if (focusState == FOCUS_COMPLETE) { // enable option to have a refocus event occur if HFR goes over threshold m_AutoFocusReady = true; //if (HFRPixels->value() == 0.0 && fileHFR == 0.0) if (fileHFR == 0.0) { QList filterHFRList; if (m_CurrentFilterPosition > 0) { // If we are using filters, then we retrieve which filter is currently active. // We check if filter lock is used, and store that instead of the current filter. // e.g. If current filter HA, but lock filter is L, then the HFR value is stored for L filter. // If no lock filter exists, then we store as is (HA) QString currentFilterText = FilterPosCombo->itemText(m_CurrentFilterPosition-1); //QString filterLock = filterManager.data()->getFilterLock(currentFilterText); //QString finalFilter = (filterLock == "--" ? currentFilterText : filterLock); //filterHFRList = HFRMap[finalFilter]; filterHFRList = HFRMap[currentFilterText]; filterHFRList.append(focusHFR); //HFRMap[finalFilter] = filterHFRList; HFRMap[currentFilterText] = filterHFRList; } // No filters else { filterHFRList = HFRMap["--"]; filterHFRList.append(focusHFR); HFRMap["--"] = filterHFRList; } double median = focusHFR; int count = filterHFRList.size(); if (Options::useMedianFocus() && count > 1) median = (count % 2) ? filterHFRList[count/2] : (filterHFRList[count/2-1] + filterHFRList[count/2])/2.0; // Add 2.5% (default) to the automatic initial HFR value to allow for minute changes in HFR without need to refocus // in case in-sequence-focusing is used. HFRPixels->setValue(median + (median * (Options::hFRThresholdPercentage() / 100.0))); } #if 0 if (focusHFR > 0 && firstAutoFocus && HFRPixels->value() == 0 && fileHFR == 0) { firstAutoFocus = false; // Add 2.5% (default) to the automatic initial HFR value to allow for minute changes in HFR without need to refocus // in case in-sequence-focusing is used. HFRPixels->setValue(focusHFR + (focusHFR * (Options::hFRThresholdPercentage() / 100.0))); } #endif // successful focus so reset elapsed time restartRefocusEveryNTimer(); } #if 0 if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || activeJob->getStatus() == SequenceJob::JOB_IDLE)) { if (focusState == FOCUS_COMPLETE) { //HFRPixels->setValue(focusHFR + (focusHFR * 0.025)); appendLogText(i18n("Focus complete.")); } else if (focusState == FOCUS_FAILED) { appendLogText(i18n("Autofocus failed. Aborting exposure...")); secondsLabel->setText(""); abort(); } return; } #endif if ((isRefocus || isInSequenceFocus) && activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY) { secondsLabel->setText(QString()); if (focusState == FOCUS_COMPLETE) { appendLogText(i18n("Focus complete.")); startNextExposure(); } else if (focusState == FOCUS_FAILED) { appendLogText(i18n("Autofocus failed. Aborting exposure...")); abort(); } } } void Capture::updateHFRThreshold() { if (fileHFR != 0.0) return; QList filterHFRList; if (FilterPosCombo->currentIndex() != -1) { // If we are using filters, then we retrieve which filter is currently active. // We check if filter lock is used, and store that instead of the current filter. // e.g. If current filter HA, but lock filter is L, then the HFR value is stored for L filter. // If no lock filter exists, then we store as is (HA) QString currentFilterText = FilterPosCombo->currentText(); QString filterLock = filterManager.data()->getFilterLock(currentFilterText); QString finalFilter = (filterLock == "--" ? currentFilterText : filterLock); filterHFRList = HFRMap[finalFilter]; } // No filters else { filterHFRList = HFRMap["--"]; } double median = 0; int count = filterHFRList.size(); if (count > 1) median = (count % 2) ? filterHFRList[count/2] : (filterHFRList[count/2-1] + filterHFRList[count/2])/2.0; else if (count == 1) median = filterHFRList[0]; // Add 2.5% (default) to the automatic initial HFR value to allow for minute changes in HFR without need to refocus // in case in-sequence-focusing is used. HFRPixels->setValue(median + (median * (Options::hFRThresholdPercentage() / 100.0))); } SkyPoint Capture::getInitialMountCoords() const { QVariant const result = mountInterface->property("currentTarget"); SkyPoint point = result.value(); return point; } -bool Capture::executeMeridianFlip() { +bool Capture::executeMeridianFlip() +{ QDBusReply const reply = mountInterface->call("executeMeridianFlip"); if (reply.error().type() == QDBusError::NoError) return reply.value(); // error occured qCCritical(KSTARS_EKOS_CAPTURE) << QString("Warning: execute meridian flip request received DBUS error: %1").arg(QDBusError::errorString(reply.error().type())); return false; } int Capture::getTotalFramesCount(QString signature) { int result = 0; bool found = false; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { // FIXME: this should be part of SequenceJob QString sig = job->getSignature(); if (sig == signature) { result += job->getCount(); found = true; } } if (found) return result; else return -1; } -void Capture::setRotator(ISD::GDInterface *newRotator) +void Capture::setRotator(ISD::GDInterface * newRotator) { currentRotator = newRotator; connect(currentRotator, &ISD::GDInterface::numberUpdated, this, &Ekos::Capture::updateRotatorNumber, Qt::UniqueConnection); rotatorB->setEnabled(true); rotatorSettings->setRotator(newRotator); - INumberVectorProperty *nvp = newRotator->getBaseDevice()->getNumber("ABS_ROTATOR_ANGLE"); + INumberVectorProperty * nvp = newRotator->getBaseDevice()->getNumber("ABS_ROTATOR_ANGLE"); rotatorSettings->setCurrentAngle(nvp->np[0].value); } -void Capture::setTelescope(ISD::GDInterface *newTelescope) +void Capture::setTelescope(ISD::GDInterface * newTelescope) { currentTelescope = static_cast(newTelescope); currentTelescope->disconnect(this); connect(currentTelescope, &ISD::GDInterface::numberUpdated, this, &Ekos::Capture::processTelescopeNumber); - connect(currentTelescope, &ISD::Telescope::newTarget, [&](const QString &target) { + connect(currentTelescope, &ISD::Telescope::newTarget, [&](const QString &target) + { if (m_State == CAPTURE_IDLE) prefixIN->setText(target); }); meridianHours->setEnabled(true); syncTelescopeInfo(); } void Capture::syncTelescopeInfo() { if (currentTelescope && currentTelescope->isConnected()) { // Sync ALL CCDs to current telescope - for (ISD::CCD *oneCCD : CCDs) + for (ISD::CCD * oneCCD : CCDs) { - ITextVectorProperty *activeDevices = oneCCD->getBaseDevice()->getText("ACTIVE_DEVICES"); + ITextVectorProperty * activeDevices = oneCCD->getBaseDevice()->getText("ACTIVE_DEVICES"); if (activeDevices) { - IText *activeTelescope = IUFindText(activeDevices, "ACTIVE_TELESCOPE"); + IText * activeTelescope = IUFindText(activeDevices, "ACTIVE_TELESCOPE"); if (activeTelescope) { IUSaveText(activeTelescope, currentTelescope->getDeviceName()); oneCCD->getDriverInfo()->getClientManager()->sendNewText(activeDevices); } } } } } void Capture::saveFITSDirectory() { QString dir = - QFileDialog::getExistingDirectory(KStars::Instance(), i18n("FITS Save Directory"), dirPath.toLocalFile()); + QFileDialog::getExistingDirectory(KStars::Instance(), i18n("FITS Save Directory"), dirPath.toLocalFile()); if (dir.isEmpty()) return; fitsDir->setText(dir); } void Capture::loadSequenceQueue() { QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Ekos Sequence Queue"), dirPath, "Ekos Sequence Queue (*.esq)"); if (fileURL.isEmpty()) return; if (fileURL.isValid() == false) { QString message = i18n("Invalid URL: %1", fileURL.toLocalFile()); KMessageBox::sorry(nullptr, message, i18n("Invalid URL")); return; } dirPath = QUrl(fileURL.url(QUrl::RemoveFilename)); loadSequenceQueue(fileURL.toLocalFile()); } bool Capture::loadSequenceQueue(const QString &fileURL) { QFile sFile(fileURL); if (!sFile.open(QIODevice::ReadOnly)) { QString message = i18n("Unable to open file %1", fileURL); KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return false; } capturedFramesMap.clear(); clearSequenceQueue(); - LilXML *xmlParser = newLilXML(); + LilXML * xmlParser = newLilXML(); char errmsg[MAXRBUF]; - XMLEle *root = nullptr; - XMLEle *ep = nullptr; + XMLEle * root = nullptr; + XMLEle * ep = nullptr; char c; // We expect all data read from the XML to be in the C locale - QLocale::c(). QLocale cLocale = QLocale::c(); while (sFile.getChar(&c)) { root = readXMLEle(xmlParser, c, errmsg); if (root) { double sqVersion = cLocale.toFloat(findXMLAttValu(root, "version")); if (sqVersion < SQ_COMPAT_VERSION) { appendLogText(i18n("Deprecated sequence file format version %1. Please construct a new sequence file.", sqVersion)); return false; } for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) { if (!strcmp(tagXMLEle(ep), "Observer")) { m_ObserverName = QString(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "GuideDeviation")) { guideDeviationCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); guideDeviation->setValue(cLocale.toDouble(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "Autofocus")) { autofocusCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); double const HFRValue = cLocale.toDouble(pcdataXMLEle(ep)); // Set the HFR value from XML, or reset it to zero, don't let another unrelated older HFR be used // Note that HFR value will only be serialized to XML when option "Save Sequence HFR to File" is enabled fileHFR = HFRValue > 0.0 ? HFRValue : 0.0; HFRPixels->setValue(fileHFR); } else if (!strcmp(tagXMLEle(ep), "RefocusEveryN")) { refocusEveryNCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); int const minutesValue = cLocale.toInt(pcdataXMLEle(ep)); // Set the refocus period from XML, or reset it to zero, don't let another unrelated older refocus period be used. refocusEveryNMinutesValue = minutesValue > 0 ? minutesValue : 0; refocusEveryN->setValue(refocusEveryNMinutesValue); } else if (!strcmp(tagXMLEle(ep), "MeridianFlip")) { meridianCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); meridianHours->setValue(cLocale.toDouble(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "CCD")) { CCDCaptureCombo->setCurrentText(pcdataXMLEle(ep)); // Signal "activated" of QComboBox does not fire when changing the text programmatically setCamera(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "FilterWheel")) { FilterDevicesCombo->setCurrentText(pcdataXMLEle(ep)); checkFilter(); } else { processJobInfo(ep); } } delXMLEle(root); } else if (errmsg[0]) { appendLogText(QString(errmsg)); delLilXML(xmlParser); return false; } } m_SequenceURL = QUrl::fromLocalFile(fileURL); m_Dirty = false; delLilXML(xmlParser); return true; } -bool Capture::processJobInfo(XMLEle *root) +bool Capture::processJobInfo(XMLEle * root) { - XMLEle *ep; - XMLEle *subEP; + XMLEle * ep; + XMLEle * subEP; rotatorSettings->setRotationEnforced(false); QLocale cLocale = QLocale::c(); for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) { if (!strcmp(tagXMLEle(ep), "Exposure")) exposureIN->setValue(cLocale.toDouble(pcdataXMLEle(ep))); else if (!strcmp(tagXMLEle(ep), "Binning")) { subEP = findXMLEle(ep, "X"); if (subEP) binXIN->setValue(cLocale.toInt(pcdataXMLEle(subEP))); subEP = findXMLEle(ep, "Y"); if (subEP) binYIN->setValue(cLocale.toInt(pcdataXMLEle(subEP))); } else if (!strcmp(tagXMLEle(ep), "Frame")) { subEP = findXMLEle(ep, "X"); if (subEP) frameXIN->setValue(cLocale.toInt(pcdataXMLEle(subEP))); subEP = findXMLEle(ep, "Y"); if (subEP) frameYIN->setValue(cLocale.toInt(pcdataXMLEle(subEP))); subEP = findXMLEle(ep, "W"); if (subEP) frameWIN->setValue(cLocale.toInt(pcdataXMLEle(subEP))); subEP = findXMLEle(ep, "H"); if (subEP) frameHIN->setValue(cLocale.toInt(pcdataXMLEle(subEP))); } else if (!strcmp(tagXMLEle(ep), "Temperature")) { if (temperatureIN->isEnabled()) temperatureIN->setValue(cLocale.toDouble(pcdataXMLEle(ep))); // If force attribute exist, we change temperatureCheck, otherwise do nothing. if (!strcmp(findXMLAttValu(ep, "force"), "true")) temperatureCheck->setChecked(true); else if (!strcmp(findXMLAttValu(ep, "force"), "false")) temperatureCheck->setChecked(false); } else if (!strcmp(tagXMLEle(ep), "Filter")) { //FilterPosCombo->setCurrentIndex(atoi(pcdataXMLEle(ep))-1); FilterPosCombo->setCurrentText(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "Type")) { frameTypeCombo->setCurrentText(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "Prefix")) { subEP = findXMLEle(ep, "RawPrefix"); if (subEP) prefixIN->setText(pcdataXMLEle(subEP)); subEP = findXMLEle(ep, "FilterEnabled"); if (subEP) filterCheck->setChecked(!strcmp("1", pcdataXMLEle(subEP))); subEP = findXMLEle(ep, "ExpEnabled"); if (subEP) expDurationCheck->setChecked(!strcmp("1", pcdataXMLEle(subEP))); subEP = findXMLEle(ep, "TimeStampEnabled"); if (subEP) ISOCheck->setChecked(!strcmp("1", pcdataXMLEle(subEP))); } else if (!strcmp(tagXMLEle(ep), "Count")) { countIN->setValue(cLocale.toInt(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "Delay")) { delayIN->setValue(cLocale.toInt(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "PostCaptureScript")) { postCaptureScriptIN->setText(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "FITSDirectory")) { fitsDir->setText(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "RemoteDirectory")) { remoteDirIN->setText(pcdataXMLEle(ep)); } else if (!strcmp(tagXMLEle(ep), "UploadMode")) { uploadModeCombo->setCurrentIndex(cLocale.toInt(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "ISOIndex")) { if (ISOCombo->isEnabled()) ISOCombo->setCurrentIndex(cLocale.toInt(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "FormatIndex")) { transferFormatCombo->setCurrentIndex(cLocale.toInt(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "Rotation")) { rotatorSettings->setRotationEnforced(true); rotatorSettings->setTargetRotationPA(cLocale.toDouble(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "Properties")) { QMap> propertyMap; for (subEP = nextXMLEle(ep, 1); subEP != nullptr; subEP = nextXMLEle(ep, 0)) { QMap numbers; - XMLEle *oneNumber = nullptr; + XMLEle * oneNumber = nullptr; for (oneNumber = nextXMLEle(subEP, 1); oneNumber != nullptr; oneNumber = nextXMLEle(subEP, 0)) { - const char *name = findXMLAttValu(oneNumber, "name"); + const char * name = findXMLAttValu(oneNumber, "name"); numbers[name] = cLocale.toDouble(pcdataXMLEle(oneNumber)); } - const char *name = findXMLAttValu(subEP, "name"); + const char * name = findXMLAttValu(subEP, "name"); propertyMap[name] = numbers; } customPropertiesDialog->setCustomProperties(propertyMap); } else if (!strcmp(tagXMLEle(ep), "Calibration")) { subEP = findXMLEle(ep, "FlatSource"); if (subEP) { - XMLEle *typeEP = findXMLEle(subEP, "Type"); + XMLEle * typeEP = findXMLEle(subEP, "Type"); if (typeEP) { if (!strcmp(pcdataXMLEle(typeEP), "Manual")) flatFieldSource = SOURCE_MANUAL; else if (!strcmp(pcdataXMLEle(typeEP), "FlatCap")) flatFieldSource = SOURCE_FLATCAP; else if (!strcmp(pcdataXMLEle(typeEP), "DarkCap")) flatFieldSource = SOURCE_DARKCAP; else if (!strcmp(pcdataXMLEle(typeEP), "Wall")) { - XMLEle *azEP = findXMLEle(subEP, "Az"); - XMLEle *altEP = findXMLEle(subEP, "Alt"); + XMLEle * azEP = findXMLEle(subEP, "Az"); + XMLEle * altEP = findXMLEle(subEP, "Alt"); if (azEP && altEP) { flatFieldSource = SOURCE_WALL; wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP))); wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP))); } } else flatFieldSource = SOURCE_DAWN_DUSK; } } subEP = findXMLEle(ep, "FlatDuration"); if (subEP) { - XMLEle *typeEP = findXMLEle(subEP, "Type"); + XMLEle * typeEP = findXMLEle(subEP, "Type"); if (typeEP) { if (!strcmp(pcdataXMLEle(typeEP), "Manual")) flatFieldDuration = DURATION_MANUAL; } - XMLEle *aduEP = findXMLEle(subEP, "Value"); + XMLEle * aduEP = findXMLEle(subEP, "Value"); if (aduEP) { flatFieldDuration = DURATION_ADU; targetADU = cLocale.toDouble(pcdataXMLEle(aduEP)); } aduEP = findXMLEle(subEP, "Tolerance"); if (aduEP) { targetADUTolerance = cLocale.toDouble(pcdataXMLEle(aduEP)); } } subEP = findXMLEle(ep, "PreMountPark"); if (subEP) { if (!strcmp(pcdataXMLEle(subEP), "True")) preMountPark = true; else preMountPark = false; } subEP = findXMLEle(ep, "PreDomePark"); if (subEP) { if (!strcmp(pcdataXMLEle(subEP), "True")) preDomePark = true; else preDomePark = false; } } } addJob(false); return true; } void Capture::saveSequenceQueue() { QUrl backupCurrent = m_SequenceURL; if (m_SequenceURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || m_SequenceURL.toLocalFile().contains("/Temp")) m_SequenceURL.clear(); // If no changes made, return. if (m_Dirty == false && !m_SequenceURL.isEmpty()) return; if (m_SequenceURL.isEmpty()) { m_SequenceURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save Ekos Sequence Queue"), dirPath, - "Ekos Sequence Queue (*.esq)"); + "Ekos Sequence Queue (*.esq)"); // if user presses cancel if (m_SequenceURL.isEmpty()) { m_SequenceURL = backupCurrent; return; } dirPath = QUrl(m_SequenceURL.url(QUrl::RemoveFilename)); if (m_SequenceURL.toLocalFile().endsWith(QLatin1String(".esq")) == false) m_SequenceURL.setPath(m_SequenceURL.toLocalFile() + ".esq"); /*if (QFile::exists(sequenceURL.toLocalFile())) { int r = KMessageBox::warningContinueCancel(0, i18n("A file named \"%1\" already exists. " "Overwrite it?", sequenceURL.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; }*/ } if (m_SequenceURL.isValid()) { if ((saveSequenceQueue(m_SequenceURL.toLocalFile())) == false) { KMessageBox::error(KStars::Instance(), i18n("Failed to save sequence queue"), i18n("Save")); return; } m_Dirty = false; } else { QString message = i18n("Invalid URL: %1", m_SequenceURL.url()); KMessageBox::sorry(KStars::Instance(), message, i18n("Invalid URL")); } } void Capture::saveSequenceQueueAs() { m_SequenceURL.clear(); saveSequenceQueue(); } bool Capture::saveSequenceQueue(const QString &path) { QFile file; QString rawPrefix; bool filterEnabled, expEnabled, tsEnabled; - const QMap frameTypes = { + const QMap frameTypes = + { { "Light", FRAME_LIGHT }, { "Dark", FRAME_DARK }, { "Bias", FRAME_BIAS }, { "Flat", FRAME_FLAT } }; file.setFileName(path); if (!file.open(QIODevice::WriteOnly)) { QString message = i18n("Unable to write to file %1", path); KMessageBox::sorry(nullptr, message, i18n("Could not open file")); return false; } QTextStream outstream(&file); // We serialize sequence data to XML using the C locale QLocale cLocale = QLocale::c(); outstream << "" << endl; outstream << "" << endl; if (m_ObserverName.isEmpty() == false) outstream << "" << m_ObserverName << "" << endl; outstream << "" << CCDCaptureCombo->currentText() << "" << endl; outstream << "" << FilterDevicesCombo->currentText() << "" << endl; outstream << "" << cLocale.toString(guideDeviation->value()) << "" << endl; // Issue a warning when autofocus is enabled but Ekos options prevent HFR value from being written if (autofocusCheck->isChecked() && !Options::saveHFRToFile()) appendLogText(i18n( - "Warning: HFR-based autofocus is set but option \"Save Sequence HFR Value to File\" is not enabled. " - "Current HFR value will not be written to sequence file.")); + "Warning: HFR-based autofocus is set but option \"Save Sequence HFR Value to File\" is not enabled. " + "Current HFR value will not be written to sequence file.")); outstream << "" << cLocale.toString(Options::saveHFRToFile() ? HFRPixels->value() : 0) << "" << endl; outstream << "" << cLocale.toString(refocusEveryN->value()) << "" << endl; outstream << "" << cLocale.toString(meridianHours->value()) << "" << endl; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { job->getPrefixSettings(rawPrefix, filterEnabled, expEnabled, tsEnabled); outstream << "" << endl; outstream << "" << cLocale.toString(job->getExposure()) << "" << endl; outstream << "" << endl; outstream << "" << cLocale.toString(job->getXBin()) << "" << endl; outstream << "" << cLocale.toString(job->getXBin()) << "" << endl; outstream << "" << endl; outstream << "" << endl; outstream << "" << cLocale.toString(job->getSubX()) << "" << endl; outstream << "" << cLocale.toString(job->getSubY()) << "" << endl; outstream << "" << cLocale.toString(job->getSubW()) << "" << endl; outstream << "" << cLocale.toString(job->getSubH()) << "" << endl; outstream << "" << endl; if (job->getTargetTemperature() != Ekos::INVALID_VALUE) outstream << "" << cLocale.toString(job->getTargetTemperature()) << "" << endl; if (job->getTargetFilter() >= 0) //outstream << "" << job->getTargetFilter() << "" << endl; outstream << "" << job->getFilterName() << "" << endl; outstream << "" << frameTypes.key(job->getFrameType()) << "" << endl; outstream << "" << endl; //outstream << "" << job->getPrefix() << "" << endl; outstream << "" << rawPrefix << "" << endl; outstream << "" << (filterEnabled ? 1 : 0) << "" << endl; outstream << "" << (expEnabled ? 1 : 0) << "" << endl; outstream << "" << (tsEnabled ? 1 : 0) << "" << endl; outstream << "" << endl; outstream << "" << cLocale.toString(job->getCount()) << "" << endl; // ms to seconds outstream << "" << cLocale.toString(job->getDelay() / 1000.0) << "" << endl; if (job->getPostCaptureScript().isEmpty() == false) outstream << "" << job->getPostCaptureScript() << "" << endl; outstream << "" << job->getLocalDir() << "" << endl; outstream << "" << job->getUploadMode() << "" << endl; if (job->getRemoteDir().isEmpty() == false) outstream << "" << job->getRemoteDir() << "" << endl; if (job->getISOIndex() != -1) outstream << "" << (job->getISOIndex()) << "" << endl; outstream << "" << (job->getTransforFormat()) << "" << endl; if (job->getTargetRotation() != Ekos::INVALID_VALUE) outstream << "" << (job->getTargetRotation()) << "" << endl; QMapIterator> customIter(job->getCustomProperties()); outstream << "" << endl; while (customIter.hasNext()) { customIter.next(); outstream << "" << endl; QMap numbers = customIter.value(); QMapIterator numberIter(numbers); while (numberIter.hasNext()) { numberIter.next(); outstream << "" << cLocale.toString(numberIter.value()) << "" << endl; + << "'>" << cLocale.toString(numberIter.value()) << "" << endl; } outstream << "" << endl; } outstream << "" << endl; outstream << "" << endl; outstream << "" << endl; if (job->getFlatFieldSource() == SOURCE_MANUAL) outstream << "Manual" << endl; else if (job->getFlatFieldSource() == SOURCE_FLATCAP) outstream << "FlatCap" << endl; else if (job->getFlatFieldSource() == SOURCE_DARKCAP) outstream << "DarkCap" << endl; else if (job->getFlatFieldSource() == SOURCE_WALL) { outstream << "Wall" << endl; outstream << "" << cLocale.toString(job->getWallCoord().az().Degrees()) << "" << endl; outstream << "" << cLocale.toString(job->getWallCoord().alt().Degrees()) << "" << endl; } else outstream << "DawnDust" << endl; outstream << "" << endl; outstream << "" << endl; if (job->getFlatFieldDuration() == DURATION_MANUAL) outstream << "Manual" << endl; else { outstream << "ADU" << endl; outstream << "" << cLocale.toString(job->getTargetADU()) << "" << endl; outstream << "" << cLocale.toString(job->getTargetADUTolerance()) << "" << endl; } outstream << "" << endl; outstream << "" << (job->isPreMountPark() ? "True" : "False") << "" << endl; outstream << "" << (job->isPreDomePark() ? "True" : "False") << "" << endl; outstream << "" << endl; outstream << "" << endl; } outstream << "" << endl; appendLogText(i18n("Sequence queue saved to %1", path)); file.close(); return true; } void Capture::resetJobs() { // Stop any running capture stop(); // If a job is selected for edit, reset only that job if (m_JobUnderEdit == true) { SequenceJob * job = jobs.at(queueTable->currentRow()); if (nullptr != job) job->resetStatus(); } else { if (KMessageBox::warningContinueCancel( nullptr, i18n("Are you sure you want to reset status of all jobs?"), i18n("Reset job status"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "reset_job_status_warning") != KMessageBox::Continue) { return; } - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) job->resetStatus(); } // Also reset the storage count for all jobs capturedFramesMap.clear(); // We're not controlled by the Scheduler, restore progress option ignoreJobProgress = Options::alwaysResetSequenceWhenStarting(); } void Capture::ignoreSequenceHistory() { // This function is called independently from the Scheduler or the UI, so honor the change ignoreJobProgress = true; } -void Capture::syncGUIToJob(SequenceJob *job) +void Capture::syncGUIToJob(SequenceJob * job) { QString rawPrefix; bool filterEnabled, expEnabled, tsEnabled; job->getPrefixSettings(rawPrefix, filterEnabled, expEnabled, tsEnabled); exposureIN->setValue(job->getExposure()); binXIN->setValue(job->getXBin()); binYIN->setValue(job->getYBin()); frameXIN->setValue(job->getSubX()); frameYIN->setValue(job->getSubY()); frameWIN->setValue(job->getSubW()); frameHIN->setValue(job->getSubH()); FilterPosCombo->setCurrentIndex(job->getTargetFilter() - 1); frameTypeCombo->setCurrentIndex(job->getFrameType()); prefixIN->setText(rawPrefix); filterCheck->setChecked(filterEnabled); expDurationCheck->setChecked(expEnabled); ISOCheck->setChecked(tsEnabled); countIN->setValue(job->getCount()); delayIN->setValue(job->getDelay() / 1000); postCaptureScriptIN->setText(job->getPostCaptureScript()); uploadModeCombo->setCurrentIndex(job->getUploadMode()); remoteDirIN->setEnabled(uploadModeCombo->currentIndex() != 0); remoteDirIN->setText(job->getRemoteDir()); fitsDir->setText(job->getLocalDir()); // Temperature Options temperatureCheck->setChecked(job->getEnforceTemperature()); if (job->getEnforceTemperature()) temperatureIN->setValue(job->getTargetTemperature()); // Flat field options calibrationB->setEnabled(job->getFrameType() != FRAME_LIGHT); flatFieldDuration = job->getFlatFieldDuration(); flatFieldSource = job->getFlatFieldSource(); targetADU = job->getTargetADU(); targetADUTolerance = job->getTargetADUTolerance(); wallCoord = job->getWallCoord(); preMountPark = job->isPreMountPark(); preDomePark = job->isPreDomePark(); // Custom Properties customPropertiesDialog->setCustomProperties(job->getCustomProperties()); if (ISOCombo->isEnabled()) ISOCombo->setCurrentIndex(job->getISOIndex()); transferFormatCombo->setCurrentIndex(job->getTransforFormat()); if (job->getTargetRotation() != Ekos::INVALID_VALUE) { rotatorSettings->setRotationEnforced(true); rotatorSettings->setTargetRotationPA(job->getTargetRotation()); } else rotatorSettings->setRotationEnforced(false); QJsonObject settings; settings.insert("camera", CCDCaptureCombo->currentText()); settings.insert("fw", FilterDevicesCombo->currentText()); settings.insert("filter", FilterPosCombo->currentText()); settings.insert("exp", exposureIN->value()); settings.insert("bin", binXIN->value()); settings.insert("iso", ISOCombo->currentIndex()); settings.insert("frameType", frameTypeCombo->currentIndex()); settings.insert("targetTemperature", temperatureIN->value()); emit settingsUpdated(settings); } void Capture::editJob(QModelIndex i) { - SequenceJob *job = jobs.at(i.row()); + SequenceJob * job = jobs.at(i.row()); if (job == nullptr) return; syncGUIToJob(job); appendLogText(i18n("Editing job #%1...", i.row() + 1)); addToQueueB->setIcon(QIcon::fromTheme("dialog-ok-apply")); addToQueueB->setToolTip(i18n("Apply job changes.")); removeFromQueueB->setToolTip(i18n("Cancel job changes.")); m_JobUnderEdit = true; } void Capture::resetJobEdit() { if (m_JobUnderEdit) appendLogText(i18n("Editing job canceled.")); m_JobUnderEdit = false; addToQueueB->setIcon(QIcon::fromTheme("list-add")); addToQueueB->setToolTip(i18n("Add job to sequence queue")); removeFromQueueB->setToolTip(i18n("Remove job from sequence queue")); } void Capture::constructPrefix(QString &imagePrefix) { if (imagePrefix.isEmpty() == false) imagePrefix += '_'; imagePrefix += frameTypeCombo->currentText(); /*if (filterCheck->isChecked() && FilterPosCombo->currentText().isEmpty() == false && frameTypeCombo->currentText().compare("Bias", Qt::CaseInsensitive) && frameTypeCombo->currentText().compare("Dark", Qt::CaseInsensitive))*/ if (filterCheck->isChecked() && FilterPosCombo->currentText().isEmpty() == false && (frameTypeCombo->currentIndex() == FRAME_LIGHT || frameTypeCombo->currentIndex() == FRAME_FLAT)) { imagePrefix += '_'; imagePrefix += FilterPosCombo->currentText(); } if (expDurationCheck->isChecked()) { //if (imagePrefix.isEmpty() == false || frameTypeCheck->isChecked()) imagePrefix += '_'; double exposureValue = exposureIN->value(); // Don't use the locale for exposure value in the capture file name, so that we get a "." as decimal separator if (exposureValue == static_cast(exposureValue)) // Whole number imagePrefix += QString::number(exposureIN->value(), 'd', 0) + QString("_secs"); else // Decimal imagePrefix += QString::number(exposureIN->value(), 'f', 3) + QString("_secs"); } if (ISOCheck->isChecked()) { imagePrefix += SequenceJob::ISOMarker; } } double Capture::getProgressPercentage() { int totalImageCount = 0; int totalImageCompleted = 0; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { totalImageCount += job->getCount(); totalImageCompleted += job->getCompleted(); } if (totalImageCount != 0) return ((static_cast(totalImageCompleted) / totalImageCount) * 100.0); else return -1; } int Capture::getActiveJobID() { if (activeJob == nullptr) return -1; for (int i = 0; i < jobs.count(); i++) { if (activeJob == jobs[i]) return i; } return -1; } int Capture::getPendingJobCount() { int completedJobs = 0; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { if (job->getStatus() == SequenceJob::JOB_DONE) completedJobs++; } return (jobs.count() - completedJobs); } QString Capture::getJobState(int id) { if (id < jobs.count()) { - SequenceJob *job = jobs.at(id); + SequenceJob * job = jobs.at(id); return job->getStatusString(); } return QString(); } int Capture::getJobImageProgress(int id) { if (id < jobs.count()) { - SequenceJob *job = jobs.at(id); + SequenceJob * job = jobs.at(id); return job->getCompleted(); } return -1; } int Capture::getJobImageCount(int id) { if (id < jobs.count()) { - SequenceJob *job = jobs.at(id); + SequenceJob * job = jobs.at(id); return job->getCount(); } return -1; } double Capture::getJobExposureProgress(int id) { if (id < jobs.count()) { - SequenceJob *job = jobs.at(id); + SequenceJob * job = jobs.at(id); return job->getExposeLeft(); } return -1; } double Capture::getJobExposureDuration(int id) { if (id < jobs.count()) { - SequenceJob *job = jobs.at(id); + SequenceJob * job = jobs.at(id); return job->getExposure(); } return -1; } -int Capture::getJobRemainingTime(SequenceJob *job) +int Capture::getJobRemainingTime(SequenceJob * job) { int remaining = 0; if (job->getStatus() == SequenceJob::JOB_BUSY) remaining += (job->getExposure() + job->getDelay() / 1000) * (job->getCount() - job->getCompleted()) + - job->getExposeLeft(); + job->getExposeLeft(); else remaining += (job->getExposure() + job->getDelay() / 1000) * (job->getCount() - job->getCompleted()); return remaining; } int Capture::getOverallRemainingTime() { double remaining = 0; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) remaining += getJobRemainingTime(job); return remaining; } int Capture::getActiveJobRemainingTime() { if (activeJob == nullptr) return -1; return getJobRemainingTime(activeJob); } void Capture::setMaximumGuidingDeviation(bool enable, double value) { guideDeviationCheck->setChecked(enable); if (enable) guideDeviation->setValue(value); } void Capture::setInSequenceFocus(bool enable, double HFR) { autofocusCheck->setChecked(enable); if (enable) HFRPixels->setValue(HFR); } void Capture::setTargetTemperature(double temperature) { temperatureIN->setValue(temperature); } void Capture::clearSequenceQueue() { activeJob = nullptr; //m_TargetName.clear(); //stop(); qDeleteAll(jobs); jobs.clear(); while (queueTable->rowCount() > 0) queueTable->removeRow(0); } QString Capture::getSequenceQueueStatus() { if (jobs.count() == 0) return "Invalid"; if (isBusy) return "Running"; int idle = 0, error = 0, complete = 0, aborted = 0, running = 0; - foreach (SequenceJob *job, jobs) + foreach (SequenceJob * job, jobs) { switch (job->getStatus()) { - case SequenceJob::JOB_ABORTED: - aborted++; - break; - case SequenceJob::JOB_BUSY: - running++; - break; - case SequenceJob::JOB_DONE: - complete++; - break; - case SequenceJob::JOB_ERROR: - error++; - break; - case SequenceJob::JOB_IDLE: - idle++; - break; + case SequenceJob::JOB_ABORTED: + aborted++; + break; + case SequenceJob::JOB_BUSY: + running++; + break; + case SequenceJob::JOB_DONE: + complete++; + break; + case SequenceJob::JOB_ERROR: + error++; + break; + case SequenceJob::JOB_IDLE: + idle++; + break; } } if (error > 0) return "Error"; if (aborted > 0) { //if (guideState >= GUIDE_GUIDING && deviationDetected) if (m_State == CAPTURE_SUSPENDED) return "Suspended"; else return "Aborted"; } if (running > 0) return "Running"; if (idle == jobs.count()) return "Idle"; if (complete == jobs.count()) return "Complete"; return "Invalid"; } -void Capture::processTelescopeNumber(INumberVectorProperty *nvp) +void Capture::processTelescopeNumber(INumberVectorProperty * nvp) { // If it is not ours, return. if (strcmp(nvp->device, currentTelescope->getDeviceName()) || strstr(nvp->name, "EQUATORIAL_") == nullptr) return; switch (meridianFlipStage) { - case MF_NONE: - break; - case MF_INITIATED: - { - if (nvp->s == IPS_BUSY) - meridianFlipStage = MF_FLIPPING; - } + case MF_NONE: + break; + case MF_INITIATED: + { + if (nvp->s == IPS_BUSY) + meridianFlipStage = MF_FLIPPING; + } break; - case MF_FLIPPING: - { - double ra, dec; - currentTelescope->getEqCoords(&ra, &dec); - double diffRA = getInitialMountCoords().ra().Hours() - ra; - // If the mount is actually flipping then we should see a difference in RA - // which if it exceeded MF_RA_DIFF_LIMIT (4 hours) then we consider it to be - // undertaking the flip. Otherwise, it's not flipping and let timeout takes care of - // of that - // Are there any mounts that do NOT change RA while flipping? i.e. do it silently? - // Need to investigate that bit - if (fabs(diffRA) > MF_RA_DIFF_LIMIT /* || nvp->s == IPS_OK*/) - meridianFlipStage = MF_SLEWING; - } + case MF_FLIPPING: + { + double ra, dec; + currentTelescope->getEqCoords(&ra, &dec); + double diffRA = getInitialMountCoords().ra().Hours() - ra; + // If the mount is actually flipping then we should see a difference in RA + // which if it exceeded MF_RA_DIFF_LIMIT (4 hours) then we consider it to be + // undertaking the flip. Otherwise, it's not flipping and let timeout takes care of + // of that + // Are there any mounts that do NOT change RA while flipping? i.e. do it silently? + // Need to investigate that bit + if (fabs(diffRA) > MF_RA_DIFF_LIMIT /* || nvp->s == IPS_OK*/) + meridianFlipStage = MF_SLEWING; + } break; - case MF_SLEWING: + case MF_SLEWING: - if (nvp->s != IPS_OK) - break; + if (nvp->s != IPS_OK) + break; - // If dome is syncing, wait until it stops - if (dome && dome->isMoving()) - break; + // If dome is syncing, wait until it stops + if (dome && dome->isMoving()) + break; - appendLogText(i18n("Telescope completed the meridian flip.")); + appendLogText(i18n("Telescope completed the meridian flip.")); - //KNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed")); - KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"), KSNotification::EVENT_INFO); + //KNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed")); + KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"), KSNotification::EVENT_INFO); - if (resumeAlignmentAfterFlip == true) - { - appendLogText(i18n("Performing post flip re-alignment...")); - secondsLabel->setText(i18n("Aligning...")); + if (resumeAlignmentAfterFlip == true) + { + appendLogText(i18n("Performing post flip re-alignment...")); + secondsLabel->setText(i18n("Aligning...")); - retries = 0; - m_State = CAPTURE_ALIGNING; - emit newStatus(Ekos::CAPTURE_ALIGNING); + retries = 0; + m_State = CAPTURE_ALIGNING; + emit newStatus(Ekos::CAPTURE_ALIGNING); - meridianFlipStage = MF_ALIGNING; - //QTimer::singleShot(Options::settlingTime(), [this]() {emit meridialFlipTracked();}); - //emit meridialFlipTracked(); - return; - } + meridianFlipStage = MF_ALIGNING; + //QTimer::singleShot(Options::settlingTime(), [this]() {emit meridialFlipTracked();}); + //emit meridialFlipTracked(); + return; + } - retries = 0; - checkGuidingAfterFlip(); - break; + retries = 0; + checkGuidingAfterFlip(); + break; - default: - break; + default: + break; } } void Capture::checkGuidingAfterFlip() { // If we're not autoguiding then we're done if (resumeGuidingAfterFlip == false) { resumeSequence(); // N.B. Set meridian flip stage AFTER resumeSequence() always meridianFlipStage = MF_NONE; } else { appendLogText(i18n("Performing post flip re-calibration and guiding...")); secondsLabel->setText(i18n("Calibrating...")); m_State = CAPTURE_CALIBRATING; emit newStatus(Ekos::CAPTURE_CALIBRATING); meridianFlipStage = MF_GUIDING; emit meridianFlipCompleted(); } } double Capture::getCurrentHA() { QVariant HA = mountInterface->property("hourAngle"); return HA.toDouble(); } double Capture::getInitialHA() { QVariant HA = mountInterface->property("initialHA"); return HA.toDouble(); } bool Capture::slew(const SkyPoint target) { QList telescopeSlew; telescopeSlew.append(target.ra().Hours()); telescopeSlew.append(target.dec().Degrees()); QDBusReply const slewModeReply = mountInterface->callWithArgumentList(QDBus::AutoDetect, "slew", telescopeSlew); if (slewModeReply.error().type() == QDBusError::NoError) return true; // error occured qCCritical(KSTARS_EKOS_CAPTURE) << QString("Warning: slew request received DBUS error: %1").arg(QDBusError::errorString(slewModeReply.error().type())); return false; } bool Capture::checkMeridianFlip() { if (currentTelescope == nullptr || meridianCheck->isChecked() == false || getInitialHA() > 0) return false; // If active job is taking flat field image at a wall source // then do not flip. if (activeJob && activeJob->getFrameType() == FRAME_FLAT && activeJob->getFlatFieldSource() == SOURCE_WALL) return false; double currentHA = getCurrentHA(); //appendLogText(i18n("Current hour angle %1", currentHA)); if (currentHA == Ekos::INVALID_VALUE) return false; if (currentHA > meridianHours->value()) { //NOTE: DO NOT make the follow sentence PLURAL as the value is in double appendLogText( - i18n("Current hour angle %1 hours exceeds meridian flip limit of %2 hours. Auto meridian flip is initiated.", - QString("%L1").arg(currentHA, 0, 'f', 2), QString("%L1").arg(meridianHours->value(), 0, 'f', 2))); + i18n("Current hour angle %1 hours exceeds meridian flip limit of %2 hours. Auto meridian flip is initiated.", + QString("%L1").arg(currentHA, 0, 'f', 2), QString("%L1").arg(meridianHours->value(), 0, 'f', 2))); meridianFlipStage = MF_INITIATED; //KNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started")); KSNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started"), KSNotification::EVENT_INFO); // Suspend guiding first before commanding a meridian flip //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) // emit suspendGuiding(false); // If we are autoguiding, we should resume autoguiding after flip resumeGuidingAfterFlip = (guideState == GUIDE_GUIDING); emit meridianFlipStarted(); // Reset frame if we need to do focusing later on if (isInSequenceFocus || (refocusEveryNCheck->isChecked() && getRefocusEveryNTimerElapsedSec() > 0)) emit resetFocus(); if (executeMeridianFlip()) { secondsLabel->setText(i18n("Meridian Flip...")); retries = 0; m_State = CAPTURE_MERIDIAN_FLIP; emit newStatus(Ekos::CAPTURE_MERIDIAN_FLIP); QTimer::singleShot(MF_TIMER_TIMEOUT, this, &Ekos::Capture::checkMeridianFlipTimeout); return true; } } return false; } void Capture::checkMeridianFlipTimeout() { if (meridianFlipStage == MF_NONE) return; if (meridianFlipStage < MF_ALIGNING) { appendLogText(i18n("Telescope meridian flip timed out. Please make sure your mount supports meridian flip.")); if (++retries == 3) { //KNotification::event(QLatin1String("MeridianFlipFailed"), i18n("Meridian flip failed")); KSNotification::event(QLatin1String("MeridianFlipFailed"), i18n("Meridian flip failed"), KSNotification::EVENT_ALERT); abort(); } else { if (executeMeridianFlip()) appendLogText(i18n("Retrying meridian flip again...")); } } } void Capture::checkGuideDeviationTimeout() { if (activeJob && activeJob->getStatus() == SequenceJob::JOB_ABORTED && m_DeviationDetected) { appendLogText(i18n("Guide module timed out.")); m_DeviationDetected = false; // If capture was suspended, it should be aborted (failed) now. if (m_State == CAPTURE_SUSPENDED) { m_State = CAPTURE_ABORTED; emit newStatus(m_State); } } } void Capture::setAlignStatus(AlignState state) { alignState = state; resumeAlignmentAfterFlip = true; switch (state) { - case ALIGN_COMPLETE: - if (meridianFlipStage == MF_ALIGNING) - { - appendLogText(i18n("Post flip re-alignment completed successfully.")); - retries = 0; - checkGuidingAfterFlip(); - } - break; - - case ALIGN_FAILED: - // TODO run it 3 times before giving up - if (meridianFlipStage == MF_ALIGNING) - { - if (++retries == 3) + case ALIGN_COMPLETE: + if (meridianFlipStage == MF_ALIGNING) { - appendLogText(i18n("Post-flip alignment failed.")); - abort(); + appendLogText(i18n("Post flip re-alignment completed successfully.")); + retries = 0; + checkGuidingAfterFlip(); } - else + break; + + case ALIGN_FAILED: + // TODO run it 3 times before giving up + if (meridianFlipStage == MF_ALIGNING) { - appendLogText(i18n("Post-flip alignment failed. Retrying...")); - secondsLabel->setText(i18n("Aligning...")); + if (++retries == 3) + { + appendLogText(i18n("Post-flip alignment failed.")); + abort(); + } + else + { + appendLogText(i18n("Post-flip alignment failed. Retrying...")); + secondsLabel->setText(i18n("Aligning...")); - this->m_State = CAPTURE_ALIGNING; - emit newStatus(Ekos::CAPTURE_ALIGNING); + this->m_State = CAPTURE_ALIGNING; + emit newStatus(Ekos::CAPTURE_ALIGNING); - meridianFlipStage = MF_ALIGNING; + meridianFlipStage = MF_ALIGNING; + } } - } - break; + break; - default: - break; + default: + break; } } void Capture::setGuideStatus(GuideState state) { switch (state) { - case GUIDE_IDLE: - case GUIDE_ABORTED: - // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip) - if (guideState == GUIDE_GUIDING && meridianFlipStage == MF_NONE && activeJob && - activeJob->getStatus() == SequenceJob::JOB_BUSY) - { - appendLogText(i18n("Autoguiding stopped. Aborting...")); - abort(); - autoGuideAbortedCapture = true; - } - break; + case GUIDE_IDLE: + case GUIDE_ABORTED: + // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip) + if (guideState == GUIDE_GUIDING && meridianFlipStage == MF_NONE && activeJob && + activeJob->getStatus() == SequenceJob::JOB_BUSY) + { + appendLogText(i18n("Autoguiding stopped. Aborting...")); + abort(); + autoGuideAbortedCapture = true; + } + break; - case GUIDE_GUIDING: - case GUIDE_CALIBRATION_SUCESS: - // If capture was aborted due to guide abort - // then let's resume capture once we are guiding again. - if (autoGuideAbortedCapture && - (guideState == GUIDE_ABORTED || guideState == GUIDE_IDLE) && - (this->m_State == CAPTURE_ABORTED || this->m_State == CAPTURE_SUSPENDED)) - { - start(); - autoGuideAbortedCapture = false; - } + case GUIDE_GUIDING: + case GUIDE_CALIBRATION_SUCESS: + // If capture was aborted due to guide abort + // then let's resume capture once we are guiding again. + if (autoGuideAbortedCapture && + (guideState == GUIDE_ABORTED || guideState == GUIDE_IDLE) && + (this->m_State == CAPTURE_ABORTED || this->m_State == CAPTURE_SUSPENDED)) + { + start(); + autoGuideAbortedCapture = false; + } - autoGuideReady = true; - break; + autoGuideReady = true; + break; - case GUIDE_CALIBRATION_ERROR: - // TODO try restarting calibration a couple of times before giving up - if (meridianFlipStage == MF_GUIDING) - { - if (++retries == 3) + case GUIDE_CALIBRATION_ERROR: + // TODO try restarting calibration a couple of times before giving up + if (meridianFlipStage == MF_GUIDING) { - appendLogText(i18n("Post meridian flip calibration error. Aborting...")); - abort(); + if (++retries == 3) + { + appendLogText(i18n("Post meridian flip calibration error. Aborting...")); + abort(); + } + else + { + appendLogText(i18n("Post meridian flip calibration error. Restarting...")); + checkGuidingAfterFlip(); + } + } + autoGuideReady = false; + break; + + case GUIDE_DITHERING_SUCCESS: + if (Options::guidingSettle() > 0) + { + // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. + appendLogText(i18n("Dither complete. Resuming capture in %1 seconds...", Options::guidingSettle())); + QTimer::singleShot(Options::guidingSettle()*1000, this, &Ekos::Capture::resumeCapture); } else { - appendLogText(i18n("Post meridian flip calibration error. Restarting...")); - checkGuidingAfterFlip(); + appendLogText(i18n("Dither complete.")); + resumeCapture(); } - } - autoGuideReady = false; - break; - - case GUIDE_DITHERING_SUCCESS: - if (Options::guidingSettle() > 0) - { - // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. - appendLogText(i18n("Dither complete. Resuming capture in %1 seconds...", Options::guidingSettle())); - QTimer::singleShot(Options::guidingSettle()*1000, this, &Ekos::Capture::resumeCapture); - } - else - { - appendLogText(i18n("Dither complete.")); - resumeCapture(); - } - break; + break; - case GUIDE_DITHERING_ERROR: - if (Options::guidingSettle() > 0) - { - // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. - appendLogText(i18n("Warning: Dithering failed. Resuming capture in %1 seconds...", Options::guidingSettle())); - QTimer::singleShot(Options::guidingSettle()*1000, this, &Ekos::Capture::resumeCapture); - } - else - { - appendLogText(i18n("Warning: Dithering failed.")); - resumeCapture(); - } + case GUIDE_DITHERING_ERROR: + if (Options::guidingSettle() > 0) + { + // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. + appendLogText(i18n("Warning: Dithering failed. Resuming capture in %1 seconds...", Options::guidingSettle())); + QTimer::singleShot(Options::guidingSettle()*1000, this, &Ekos::Capture::resumeCapture); + } + else + { + appendLogText(i18n("Warning: Dithering failed.")); + resumeCapture(); + } - break; + break; - default: - break; + default: + break; } guideState = state; } void Capture::checkFrameType(int index) { if (index == FRAME_LIGHT) calibrationB->setEnabled(false); else calibrationB->setEnabled(true); } double Capture::setCurrentADU(double value) { double nextExposure = 0; double targetADU = activeJob->getTargetADU(); std::vector coeff; // Check if saturated, then take shorter capture and discard value ExpRaw.append(activeJob->getExposure()); ADURaw.append(value); qCDebug(KSTARS_EKOS_CAPTURE) << "Capture: Current ADU = " << value << " targetADU = " << targetADU << " Exposure Count: " << ExpRaw.count(); // Most CCDs are quite linear so 1st degree polynomial is quite sufficient // But DSLRs can exhibit non-linear response curve and so a 2nd degree polynomial is more appropriate if (ExpRaw.count() >= 2) { if (ExpRaw.count() >= 5) { double chisq = 0; coeff = gsl_polynomial_fit(ADURaw.data(), ExpRaw.data(), ExpRaw.count(), 2, chisq); qCDebug(KSTARS_EKOS_CAPTURE) << "Running polynomial fitting. Found " << coeff.size() << " coefficients."; for (size_t i = 0; i < coeff.size(); i++) qCDebug(KSTARS_EKOS_CAPTURE) << "Coeff #" << i << "=" << coeff[i]; } bool looping = false; if (ExpRaw.count() >= 10) { int size = ExpRaw.count(); looping = (ExpRaw[size - 1] == ExpRaw[size - 2]) && (ExpRaw[size - 2] == ExpRaw[size - 3]); if (looping) qWarning(KSTARS_EKOS_CAPTURE) << "Detected looping in polynomial results. Falling back to llsqr."; } // If we get invalid data, let's fall back to llsq // Since polyfit can be unreliable at low counts, let's only use it at the 5th exposure // if we don't have results already. if (looping || ExpRaw.count() < 5 || std::isnan(coeff[0]) || std::isinf(coeff[0])) { double a = 0, b = 0; llsq(ExpRaw, ADURaw, a, b); qWarning(KSTARS_EKOS_CAPTURE) << "Polynomial fitting invalid, falling back to llsq. a=" << a << " b=" << b; // If we have valid results, let's calculate next exposure if (a != 0) { nextExposure = (targetADU - b) / a; // If we get invalid value, let's just proceed iteratively if (nextExposure < 0) nextExposure = 0; } } else if (coeff.size() == 3) { nextExposure = coeff[0] + (coeff[1] * targetADU) + (coeff[2] * pow(targetADU, 2)); // If we get invalid exposure time, let's try to capture again and discard last point. if (nextExposure < 0) { qCDebug(KSTARS_EKOS_CAPTURE) << "Invalid polynomial exposure" << nextExposure << "Will discard last result."; ExpRaw.removeLast(); ADURaw.removeLast(); nextExposure = activeJob->getExposure(); } } } if (nextExposure == 0) { if (value < targetADU) nextExposure = activeJob->getExposure() * 1.25; else nextExposure = activeJob->getExposure() * .75; } qCDebug(KSTARS_EKOS_CAPTURE) << "next flat exposure is" << nextExposure; return nextExposure; } // Based on John Burkardt LLSQ (LGPL) void Capture::llsq(QVector x, QVector y, double &a, double &b) { double bot; int i; double top; double xbar; double ybar; int n = x.count(); // // Special case. // if (n == 1) { a = 0.0; b = y[0]; return; } // // Average X and Y. // xbar = 0.0; ybar = 0.0; for (i = 0; i < n; i++) { xbar = xbar + x[i]; ybar = ybar + y[i]; } xbar = xbar / static_cast(n); ybar = ybar / static_cast(n); // // Compute Beta. // top = 0.0; bot = 0.0; for (i = 0; i < n; i++) { top = top + (x[i] - xbar) * (y[i] - ybar); bot = bot + (x[i] - xbar) * (x[i] - xbar); } a = top / bot; b = ybar - a * xbar; } void Capture::setDirty() { m_Dirty = true; } void Capture::setMeridianFlip(bool enable) { meridianCheck->setChecked(enable); } void Capture::setMeridianFlipHour(double hours) { meridianHours->setValue(hours); } bool Capture::hasCoolerControl() { if (currentCCD && currentCCD->hasCoolerControl()) return true; return false; } bool Capture::setCoolerControl(bool enable) { if (currentCCD && currentCCD->hasCoolerControl()) return currentCCD->setCoolerControl(enable); return false; } void Capture::clearAutoFocusHFR() { // If HFR limit was set from file, we cannot override it. if (fileHFR > 0) return; HFRPixels->setValue(0); //firstAutoFocus = true; } void Capture::openCalibrationDialog() { QDialog calibrationDialog; Ui_calibrationOptions calibrationOptions; calibrationOptions.setupUi(&calibrationDialog); if (currentTelescope) { calibrationOptions.parkMountC->setEnabled(currentTelescope->canPark()); calibrationOptions.parkMountC->setChecked(preMountPark); } else calibrationOptions.parkMountC->setEnabled(false); if (dome) { calibrationOptions.parkDomeC->setEnabled(dome->canPark()); calibrationOptions.parkDomeC->setChecked(preDomePark); } else calibrationOptions.parkDomeC->setEnabled(false); //connect(calibrationOptions.wallSourceC, SIGNAL(toggled(bool)), calibrationOptions.parkC, &Ekos::Capture::setDisabled(bool))); switch (flatFieldSource) { - case SOURCE_MANUAL: - calibrationOptions.manualSourceC->setChecked(true); - break; + case SOURCE_MANUAL: + calibrationOptions.manualSourceC->setChecked(true); + break; - case SOURCE_FLATCAP: - calibrationOptions.flatDeviceSourceC->setChecked(true); - break; + case SOURCE_FLATCAP: + calibrationOptions.flatDeviceSourceC->setChecked(true); + break; - case SOURCE_DARKCAP: - calibrationOptions.darkDeviceSourceC->setChecked(true); - break; + case SOURCE_DARKCAP: + calibrationOptions.darkDeviceSourceC->setChecked(true); + break; - case SOURCE_WALL: - calibrationOptions.wallSourceC->setChecked(true); - calibrationOptions.azBox->setText(wallCoord.az().toDMSString()); - calibrationOptions.altBox->setText(wallCoord.alt().toDMSString()); - break; + case SOURCE_WALL: + calibrationOptions.wallSourceC->setChecked(true); + calibrationOptions.azBox->setText(wallCoord.az().toDMSString()); + calibrationOptions.altBox->setText(wallCoord.alt().toDMSString()); + break; - case SOURCE_DAWN_DUSK: - calibrationOptions.dawnDuskFlatsC->setChecked(true); - break; + case SOURCE_DAWN_DUSK: + calibrationOptions.dawnDuskFlatsC->setChecked(true); + break; } switch (flatFieldDuration) { - case DURATION_MANUAL: - calibrationOptions.manualDurationC->setChecked(true); - break; + case DURATION_MANUAL: + calibrationOptions.manualDurationC->setChecked(true); + break; - case DURATION_ADU: - calibrationOptions.ADUC->setChecked(true); - calibrationOptions.ADUValue->setValue(targetADU); - calibrationOptions.ADUTolerance->setValue(targetADUTolerance); - break; + case DURATION_ADU: + calibrationOptions.ADUC->setChecked(true); + calibrationOptions.ADUValue->setValue(targetADU); + calibrationOptions.ADUTolerance->setValue(targetADUTolerance); + break; } if (calibrationDialog.exec() == QDialog::Accepted) { if (calibrationOptions.manualSourceC->isChecked()) flatFieldSource = SOURCE_MANUAL; else if (calibrationOptions.flatDeviceSourceC->isChecked()) flatFieldSource = SOURCE_FLATCAP; else if (calibrationOptions.darkDeviceSourceC->isChecked()) flatFieldSource = SOURCE_DARKCAP; else if (calibrationOptions.wallSourceC->isChecked()) { dms wallAz, wallAlt; bool azOk = false, altOk = false; wallAz = calibrationOptions.azBox->createDms(true, &azOk); wallAlt = calibrationOptions.altBox->createDms(true, &altOk); if (azOk && altOk) { flatFieldSource = SOURCE_WALL; wallCoord.setAz(wallAz); wallCoord.setAlt(wallAlt); } else { calibrationOptions.manualSourceC->setChecked(true); KMessageBox::error(this, i18n("Wall coordinates are invalid.")); } } else flatFieldSource = SOURCE_DAWN_DUSK; if (calibrationOptions.manualDurationC->isChecked()) flatFieldDuration = DURATION_MANUAL; else { flatFieldDuration = DURATION_ADU; targetADU = calibrationOptions.ADUValue->value(); targetADUTolerance = calibrationOptions.ADUTolerance->value(); } preMountPark = calibrationOptions.parkMountC->isChecked(); preDomePark = calibrationOptions.parkDomeC->isChecked(); setDirty(); Options::setCalibrationFlatSourceIndex(flatFieldSource); Options::setCalibrationFlatDurationIndex(flatFieldDuration); Options::setCalibrationWallAz(wallCoord.az().Degrees()); Options::setCalibrationWallAlt(wallCoord.alt().Degrees()); Options::setCalibrationADUValue(targetADU); Options::setCalibrationADUValueTolerance(targetADUTolerance); } } IPState Capture::processPreCaptureCalibrationStage() { // Unpark dust cap if we have to take light images. if (activeJob->getFrameType() == FRAME_LIGHT) { // step 1: unpark dust cap - if (dustCap != nullptr) { + if (dustCap != nullptr) + { if (dustCap->isLightOn() == true) { dustCapLightEnabled = false; dustCap->SetLightEnabled(false); } // If cap is not unparked, unpark it if (calibrationStage < CAL_DUSTCAP_UNPARKING && dustCap->isParked()) { if (dustCap->UnPark()) { calibrationStage = CAL_DUSTCAP_UNPARKING; appendLogText(i18n("Unparking dust cap...")); return IPS_BUSY; } else { appendLogText(i18n("Unparking dust cap failed, aborting...")); abort(); return IPS_ALERT; } } // Wait until cap is unparked if (calibrationStage == CAL_DUSTCAP_UNPARKING) { if (dustCap->isUnParked() == false) return IPS_BUSY; else { calibrationStage = CAL_DUSTCAP_UNPARKED; appendLogText(i18n("Dust cap unparked.")); } } } // step 2: check if meridian flip already is ongoing if (meridianFlipStage != MF_NONE) return IPS_BUSY; // step 3: check if meridian flip is required if (checkMeridianFlip()) return IPS_BUSY; calibrationStage = CAL_PRECAPTURE_COMPLETE; if (guideState == GUIDE_SUSPENDED) { appendLogText(i18n("Autoguiding resumed.")); emit resumeGuiding(); } return IPS_OK; } if (activeJob->getFrameType() != FRAME_LIGHT && guideState == GUIDE_GUIDING) { appendLogText(i18n("Autoguiding suspended.")); emit suspendGuiding(); } // Let's check what actions to be taken, if any, for the flat field source switch (activeJob->getFlatFieldSource()) { - case SOURCE_MANUAL: - case SOURCE_DAWN_DUSK: // Not yet implemented - break; + case SOURCE_MANUAL: + case SOURCE_DAWN_DUSK: // Not yet implemented + break; // For Dark, Flat, Bias: Park cap, if not parked. Turn on Light For Flat. Turn off Light for Bias + Darks // For Lights: Unpark cap and turn off light - case SOURCE_FLATCAP: - if (dustCap) - { - // If cap is not park, park it - if (calibrationStage < CAL_DUSTCAP_PARKING && dustCap->isParked() == false) + case SOURCE_FLATCAP: + if (dustCap) { - if (dustCap->Park()) + // If cap is not park, park it + if (calibrationStage < CAL_DUSTCAP_PARKING && dustCap->isParked() == false) { - calibrationStage = CAL_DUSTCAP_PARKING; - appendLogText(i18n("Parking dust cap...")); - return IPS_BUSY; + if (dustCap->Park()) + { + calibrationStage = CAL_DUSTCAP_PARKING; + appendLogText(i18n("Parking dust cap...")); + return IPS_BUSY; + } + else + { + appendLogText(i18n("Parking dust cap failed, aborting...")); + abort(); + return IPS_ALERT; + } } - else + + // Wait until cap is parked + if (calibrationStage == CAL_DUSTCAP_PARKING) { - appendLogText(i18n("Parking dust cap failed, aborting...")); - abort(); - return IPS_ALERT; + if (dustCap->isParked() == false) + return IPS_BUSY; + else + { + calibrationStage = CAL_DUSTCAP_PARKED; + appendLogText(i18n("Dust cap parked.")); + } } - } - // Wait until cap is parked - if (calibrationStage == CAL_DUSTCAP_PARKING) - { - if (dustCap->isParked() == false) - return IPS_BUSY; - else + // If light is not on, turn it on. For flat frames only + if (activeJob->getFrameType() == FRAME_FLAT && dustCap->isLightOn() == false) { - calibrationStage = CAL_DUSTCAP_PARKED; - appendLogText(i18n("Dust cap parked.")); + dustCapLightEnabled = true; + dustCap->SetLightEnabled(true); + } + else if (activeJob->getFrameType() != FRAME_FLAT && dustCap->isLightOn() == true) + { + dustCapLightEnabled = false; + dustCap->SetLightEnabled(false); } } - - // If light is not on, turn it on. For flat frames only - if (activeJob->getFrameType() == FRAME_FLAT && dustCap->isLightOn() == false) - { - dustCapLightEnabled = true; - dustCap->SetLightEnabled(true); - } - else if (activeJob->getFrameType() != FRAME_FLAT && dustCap->isLightOn() == true) - { - dustCapLightEnabled = false; - dustCap->SetLightEnabled(false); - } - } - break; + break; // Park cap, if not parked and not flat frame // Unpark cap, if flat frame // Turn on Light - case SOURCE_DARKCAP: - if (dustCap) - { - // If cap is not park, park it if not flat frame. (external lightsource) - if (calibrationStage < CAL_DUSTCAP_PARKING && dustCap->isParked() == false && - activeJob->getFrameType() != FRAME_FLAT) + case SOURCE_DARKCAP: + if (dustCap) { - if (dustCap->Park()) + // If cap is not park, park it if not flat frame. (external lightsource) + if (calibrationStage < CAL_DUSTCAP_PARKING && dustCap->isParked() == false && + activeJob->getFrameType() != FRAME_FLAT) { - calibrationStage = CAL_DUSTCAP_PARKING; - appendLogText(i18n("Parking dust cap...")); - return IPS_BUSY; + if (dustCap->Park()) + { + calibrationStage = CAL_DUSTCAP_PARKING; + appendLogText(i18n("Parking dust cap...")); + return IPS_BUSY; + } + else + { + appendLogText(i18n("Parking dust cap failed, aborting...")); + abort(); + return IPS_ALERT; + } } - else + + // Wait until cap is parked + if (calibrationStage == CAL_DUSTCAP_PARKING) { - appendLogText(i18n("Parking dust cap failed, aborting...")); - abort(); - return IPS_ALERT; + if (dustCap->isParked() == false) + return IPS_BUSY; + else + { + calibrationStage = CAL_DUSTCAP_PARKED; + appendLogText(i18n("Dust cap parked.")); + } } - } - // Wait until cap is parked - if (calibrationStage == CAL_DUSTCAP_PARKING) - { - if (dustCap->isParked() == false) - return IPS_BUSY; - else + // If cap is parked, unpark it if flat frame. (external lightsource) + if (calibrationStage < CAL_DUSTCAP_UNPARKING && dustCap->isParked() == true && + activeJob->getFrameType() == FRAME_FLAT) { - calibrationStage = CAL_DUSTCAP_PARKED; - appendLogText(i18n("Dust cap parked.")); + if (dustCap->UnPark()) + { + calibrationStage = CAL_DUSTCAP_UNPARKING; + appendLogText(i18n("UnParking dust cap...")); + return IPS_BUSY; + } + else + { + appendLogText(i18n("UnParking dust cap failed, aborting...")); + abort(); + return IPS_ALERT; + } } - } - // If cap is parked, unpark it if flat frame. (external lightsource) - if (calibrationStage < CAL_DUSTCAP_UNPARKING && dustCap->isParked() == true && - activeJob->getFrameType() == FRAME_FLAT) - { - if (dustCap->UnPark()) + // Wait until cap is parked + if (calibrationStage == CAL_DUSTCAP_UNPARKING) { - calibrationStage = CAL_DUSTCAP_UNPARKING; - appendLogText(i18n("UnParking dust cap...")); - return IPS_BUSY; + if (dustCap->isParked() == true) + return IPS_BUSY; + else + { + calibrationStage = CAL_DUSTCAP_UNPARKED; + appendLogText(i18n("Dust cap unparked.")); + } } - else + + // If light is not on, turn it on. For flat frames only + if (activeJob->getFrameType() == FRAME_FLAT && dustCap->isLightOn() == false) { - appendLogText(i18n("UnParking dust cap failed, aborting...")); - abort(); - return IPS_ALERT; + dustCapLightEnabled = true; + dustCap->SetLightEnabled(true); } - } - - // Wait until cap is parked - if (calibrationStage == CAL_DUSTCAP_UNPARKING) - { - if (dustCap->isParked() == true) - return IPS_BUSY; - else + else if (activeJob->getFrameType() != FRAME_FLAT && dustCap->isLightOn() == true) { - calibrationStage = CAL_DUSTCAP_UNPARKED; - appendLogText(i18n("Dust cap unparked.")); + dustCapLightEnabled = false; + dustCap->SetLightEnabled(false); } } - - // If light is not on, turn it on. For flat frames only - if (activeJob->getFrameType() == FRAME_FLAT && dustCap->isLightOn() == false) - { - dustCapLightEnabled = true; - dustCap->SetLightEnabled(true); - } - else if (activeJob->getFrameType() != FRAME_FLAT && dustCap->isLightOn() == true) - { - dustCapLightEnabled = false; - dustCap->SetLightEnabled(false); - } - } - break; + break; // Go to wall coordinates - case SOURCE_WALL: - if (currentTelescope && activeJob->getFrameType() == FRAME_FLAT) - { - if (calibrationStage < CAL_SLEWING) - { - wallCoord = activeJob->getWallCoord(); - wallCoord.HorizontalToEquatorial(KStarsData::Instance()->lst(), - KStarsData::Instance()->geo()->lat()); - currentTelescope->Slew(&wallCoord); - appendLogText(i18n("Mount slewing to wall position...")); - calibrationStage = CAL_SLEWING; - return IPS_BUSY; - } - - // Check if slewing is complete - if (calibrationStage == CAL_SLEWING) + case SOURCE_WALL: + if (currentTelescope && activeJob->getFrameType() == FRAME_FLAT) { - if (currentTelescope->isSlewing() == false) + if (calibrationStage < CAL_SLEWING) { - // Disable mount tracking if supported by the driver. - currentTelescope->setTrackEnabled(false); - calibrationStage = CAL_SLEWING_COMPLETE; - appendLogText(i18n("Slew to wall position complete.")); - } - else + wallCoord = activeJob->getWallCoord(); + wallCoord.HorizontalToEquatorial(KStarsData::Instance()->lst(), + KStarsData::Instance()->geo()->lat()); + currentTelescope->Slew(&wallCoord); + appendLogText(i18n("Mount slewing to wall position...")); + calibrationStage = CAL_SLEWING; return IPS_BUSY; - } + } - if (lightBox) - { - // Check if we have a light box to turn on - if (activeJob->getFrameType() == FRAME_FLAT && lightBox->isLightOn() == false) + // Check if slewing is complete + if (calibrationStage == CAL_SLEWING) { - lightBoxLightEnabled = true; - lightBox->SetLightEnabled(true); + if (currentTelescope->isSlewing() == false) + { + // Disable mount tracking if supported by the driver. + currentTelescope->setTrackEnabled(false); + calibrationStage = CAL_SLEWING_COMPLETE; + appendLogText(i18n("Slew to wall position complete.")); + } + else + return IPS_BUSY; } - else if (activeJob->getFrameType() != FRAME_FLAT && lightBox->isLightOn() == true) + + if (lightBox) { - lightBoxLightEnabled = false; - lightBox->SetLightEnabled(false); + // Check if we have a light box to turn on + if (activeJob->getFrameType() == FRAME_FLAT && lightBox->isLightOn() == false) + { + lightBoxLightEnabled = true; + lightBox->SetLightEnabled(true); + } + else if (activeJob->getFrameType() != FRAME_FLAT && lightBox->isLightOn() == true) + { + lightBoxLightEnabled = false; + lightBox->SetLightEnabled(false); + } } } - } - break; + break; } // Check if we need to perform mount prepark if (preMountPark && currentTelescope && activeJob->getFlatFieldSource() != SOURCE_WALL) { if (calibrationStage < CAL_MOUNT_PARKING && currentTelescope->isParked() == false) { if (currentTelescope->Park()) { calibrationStage = CAL_MOUNT_PARKING; //emit mountParking(); appendLogText(i18n("Parking mount prior to calibration frames capture...")); return IPS_BUSY; } else { appendLogText(i18n("Parking mount failed, aborting...")); abort(); return IPS_ALERT; } } if (calibrationStage == CAL_MOUNT_PARKING) { // If not parked yet, check again in 1 second // Otherwise proceed to the rest of the algorithm if (currentTelescope->isParked() == false) return IPS_BUSY; else { calibrationStage = CAL_MOUNT_PARKED; appendLogText(i18n("Mount parked.")); } } } // Check if we need to perform dome prepark if (preDomePark && dome) { if (calibrationStage < CAL_DOME_PARKING && dome->isParked() == false) { if (dome->Park()) { calibrationStage = CAL_DOME_PARKING; //emit mountParking(); appendLogText(i18n("Parking dome...")); return IPS_BUSY; } else { appendLogText(i18n("Parking dome failed, aborting...")); abort(); return IPS_ALERT; } } if (calibrationStage == CAL_DOME_PARKING) { // If not parked yet, check again in 1 second // Otherwise proceed to the rest of the algorithm if (dome->isParked() == false) return IPS_BUSY; else { calibrationStage = CAL_DOME_PARKED; appendLogText(i18n("Dome parked.")); } } } // If we used AUTOFOCUS before for a specific frame (e.g. Lum) // then the absolute focus position for Lum is recorded in the filter manager // when we take flats again, we always go back to the same focus position as the light frames to ensure // near identical focus for both frames. if (activeJob->getFrameType() == FRAME_FLAT && - m_AutoFocusReady && - currentFilter != nullptr && - Options::flatSyncFocus()) + m_AutoFocusReady && + currentFilter != nullptr && + Options::flatSyncFocus()) { if (filterManager->syncAbsoluteFocusPosition(activeJob->getTargetFilter()-1) == false) return IPS_BUSY; } calibrationStage = CAL_PRECAPTURE_COMPLETE; return IPS_OK; } bool Capture::processPostCaptureCalibrationStage() { // If there are no more images to capture, do not bother calculating next exposure if (calibrationStage == CAL_CALIBRATION_COMPLETE) if (activeJob && activeJob->getCount() <= activeJob->getCompleted()) return true; // Check if we need to do flat field slope calculation if the user specified a desired ADU value if (activeJob->getFrameType() == FRAME_FLAT && activeJob->getFlatFieldDuration() == DURATION_ADU && activeJob->getTargetADU() > 0) { if (Options::useFITSViewer() == false) { Options::setUseFITSViewer(true); qCInfo(KSTARS_EKOS_CAPTURE) << "Enabling FITS Viewer..."; } - FITSData *image_data = nullptr; - FITSView *currentImage = targetChip->getImageView(FITS_NORMAL); + FITSData * image_data = nullptr; + FITSView * currentImage = targetChip->getImageView(FITS_NORMAL); if (currentImage) { image_data = currentImage->getImageData(); double currentADU = image_data->getADU(); bool outOfRange=false, saturated=false; switch (image_data->bpp()) { case 8: - if (activeJob->getTargetADU() > UINT8_MAX) - outOfRange = true; - else if (currentADU/UINT8_MAX > 0.95) - saturated = true; - break; + if (activeJob->getTargetADU() > UINT8_MAX) + outOfRange = true; + else if (currentADU/UINT8_MAX > 0.95) + saturated = true; + break; case 16: - if (activeJob->getTargetADU() > UINT16_MAX) - outOfRange = true; - else if (currentADU/UINT16_MAX > 0.95) - saturated = true; - break; + if (activeJob->getTargetADU() > UINT16_MAX) + outOfRange = true; + else if (currentADU/UINT16_MAX > 0.95) + saturated = true; + break; case 32: - if (activeJob->getTargetADU() > UINT32_MAX) - outOfRange = true; - else if (currentADU/UINT32_MAX > 0.95) - saturated = true; - break; + if (activeJob->getTargetADU() > UINT32_MAX) + outOfRange = true; + else if (currentADU/UINT32_MAX > 0.95) + saturated = true; + break; - default: - break; + default: + break; } if (outOfRange) { appendLogText(i18n("Flat calibration failed. Captured image is only %1-bit while requested ADU is %2.", QString::number(image_data->bpp()) , QString::number(activeJob->getTargetADU(), 'f', 2))); abort(); return false; } else if (saturated) { double nextExposure = activeJob->getExposure() * 0.1; nextExposure = qBound(exposureIN->minimum(), nextExposure, exposureIN->maximum()); appendLogText(i18n("Current image is saturated (%1). Next exposure is %2 seconds.", QString::number(currentADU, 'f', 0), QString("%L1").arg(nextExposure, 0, 'f', 6))); calibrationStage = CAL_CALIBRATION; activeJob->setExposure(nextExposure); activeJob->setPreview(true); rememberUploadMode = activeJob->getUploadMode(); currentCCD->setUploadMode(ISD::CCD::UPLOAD_CLIENT); startNextExposure(); return false; } double ADUDiff = fabs(currentADU - activeJob->getTargetADU()); // If it is within tolerance range of target ADU if (ADUDiff <= targetADUTolerance) { if (calibrationStage == CAL_CALIBRATION) { appendLogText( - i18n("Current ADU %1 within target ADU tolerance range.", QString::number(currentADU, 'f', 0))); + i18n("Current ADU %1 within target ADU tolerance range.", QString::number(currentADU, 'f', 0))); activeJob->setPreview(false); currentCCD->setUploadMode(rememberUploadMode); // Get raw prefix exposureIN->setValue(activeJob->getExposure()); QString imagePrefix = activeJob->getRawPrefix(); constructPrefix(imagePrefix); activeJob->setFullPrefix(imagePrefix); seqPrefix = imagePrefix; currentCCD->setSeqPrefix(imagePrefix); currentCCD->updateUploadSettings(activeJob->getRemoteDir() + activeJob->getDirectoryPostfix()); calibrationStage = CAL_CALIBRATION_COMPLETE; startNextExposure(); return false; } return true; } double nextExposure = -1; // If value is saturated, try to reduce it to valid range first if (std::fabs(image_data->getMax(0) - image_data->getMin(0)) < 10) nextExposure = activeJob->getExposure() * 0.5; else nextExposure = setCurrentADU(currentADU); if (nextExposure <= 0 || std::isnan(nextExposure)) { appendLogText( - i18n("Unable to calculate optimal exposure settings, please capture the flats manually.")); + i18n("Unable to calculate optimal exposure settings, please capture the flats manually.")); //activeJob->setTargetADU(0); //targetADU = 0; abort(); return false; } // Limit to minimum and maximum values nextExposure = qBound(exposureIN->minimum(), nextExposure, exposureIN->maximum()); appendLogText(i18n("Current ADU is %1 Next exposure is %2 seconds.", QString::number(currentADU, 'f', 0), QString("%L1").arg(nextExposure, 0, 'f', 6))); calibrationStage = CAL_CALIBRATION; activeJob->setExposure(nextExposure); activeJob->setPreview(true); rememberUploadMode = activeJob->getUploadMode(); currentCCD->setUploadMode(ISD::CCD::UPLOAD_CLIENT); startNextExposure(); return false; // Start next exposure in case ADU Slope is not calculated yet /*if (currentSlope == 0) { startNextExposure(); return; }*/ } else { appendLogText(i18n("An empty image is received, aborting...")); abort(); return false; } } calibrationStage = CAL_CALIBRATION_COMPLETE; return true; } void Capture::setNewRemoteFile(QString file) { appendLogText(i18n("Remote image saved to %1", file)); emit newSequenceImage(file); } /* void Capture::startPostFilterAutoFocus() { if (focusState >= FOCUS_PROGRESS || state == CAPTURE_FOCUSING) return; secondsLabel->setText(i18n("Focusing...")); state = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); appendLogText(i18n("Post filter change Autofocus...")); // Force it to always run autofocus routine emit checkFocus(0.1); } */ void Capture::postScriptFinished(int exitCode) { appendLogText(i18n("Post capture script finished with code %1.", exitCode)); // If we're done, proceed to completion. if (activeJob->getCount() <= activeJob->getCompleted()) { processJobCompletion(); } // Else check if meridian condition is met. else if (checkMeridianFlip()) { appendLogText(i18n("Processing meridian flip...")); } // Then if nothing else, just resume sequence. else { appendLogText(i18n("Resuming sequence...")); resumeSequence(); } } // FIXME Migrate to Filter Manager #if 0 void Capture::loadFilterOffsets() { // Get all OAL equipment filter list KStarsData::Instance()->userdb()->GetAllFilters(m_filterList); filterFocusOffsets.clear(); for (int i = 0; i < FilterPosCombo->count(); i++) { - FocusOffset *oneOffset = new FocusOffset; + FocusOffset * oneOffset = new FocusOffset; oneOffset->filter = FilterPosCombo->itemText(i); oneOffset->offset = 0; // Find matching filter if any and loads its offset - foreach (OAL::Filter *o, m_filterList) + foreach (OAL::Filter * o, m_filterList) { if (o->vendor() == FilterCaptureCombo->currentText() && o->color() == oneOffset->filter) { oneOffset->offset = o->offset().toInt(); break; } } filterFocusOffsets.append(oneOffset); } } void Capture::showFilterOffsetDialog() { loadFilterOffsets(); QDialog filterOffsetDialog; filterOffsetDialog.setWindowTitle(i18n("Filter Focus Offsets")); - QDialogButtonBox *buttonBox = - new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &filterOffsetDialog); + QDialogButtonBox * buttonBox = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &filterOffsetDialog); connect(buttonBox, SIGNAL(accepted()), &filterOffsetDialog, &Ekos::Capture::accept())); connect(buttonBox, SIGNAL(rejected()), &filterOffsetDialog, &Ekos::Capture::reject())); - QVBoxLayout *mainLayout = new QVBoxLayout(&filterOffsetDialog); - QGridLayout *grid = new QGridLayout(&filterOffsetDialog); - QHBoxLayout *tipLayout = new QHBoxLayout(&filterOffsetDialog); + QVBoxLayout * mainLayout = new QVBoxLayout(&filterOffsetDialog); + QGridLayout * grid = new QGridLayout(&filterOffsetDialog); + QHBoxLayout * tipLayout = new QHBoxLayout(&filterOffsetDialog); - QLabel *tipIcon = new QLabel(&filterOffsetDialog); - QLabel *tipText = new QLabel(&filterOffsetDialog); + QLabel * tipIcon = new QLabel(&filterOffsetDialog); + QLabel * tipText = new QLabel(&filterOffsetDialog); tipIcon->setPixmap( - QIcon::fromTheme("kstars_flag").pixmap(QSize(32, 32))); + QIcon::fromTheme("kstars_flag").pixmap(QSize(32, 32))); tipIcon->setFixedSize(32, 32); tipText->setText(i18n("Set relative filter focus offset in steps.")); tipLayout->addWidget(tipIcon); tipLayout->addWidget(tipText); mainLayout->addLayout(grid); mainLayout->addLayout(tipLayout); mainLayout->addWidget(buttonBox); //filterOffsetDialog.setLayout(mainLayout); for (int i = 0; i < filterFocusOffsets.count(); i++) - { - FocusOffset *oneOffset = filterFocusOffsets.at(i); +{ + FocusOffset * oneOffset = filterFocusOffsets.at(i); - QLabel *label = new QLabel(oneOffset->filter, &filterOffsetDialog); - QSpinBox *spin = new QSpinBox(&filterOffsetDialog); + QLabel * label = new QLabel(oneOffset->filter, &filterOffsetDialog); + QSpinBox * spin = new QSpinBox(&filterOffsetDialog); spin->setMinimum(-10000); spin->setMaximum(10000); spin->setSingleStep(100); spin->setValue(oneOffset->offset); grid->addWidget(label, i, 0); grid->addWidget(spin, i, 1); } if (filterOffsetDialog.exec() == QDialog::Accepted) - { - for (int i = 0; i < filterFocusOffsets.count(); i++) +{ + for (int i = 0; i < filterFocusOffsets.count(); i++) { - FocusOffset *oneOffset = filterFocusOffsets.at(i); + FocusOffset * oneOffset = filterFocusOffsets.at(i); oneOffset->offset = static_cast(grid->itemAtPosition(i, 1)->widget())->value(); // Find matching filter if any and save its offset - OAL::Filter *matchedFilter = nullptr; + OAL::Filter * matchedFilter = nullptr; - foreach (OAL::Filter *o, m_filterList) + foreach (OAL::Filter * o, m_filterList) { if (o->vendor() == FilterCaptureCombo->currentText() && o->color() == oneOffset->filter) { o->setOffset(QString::number(oneOffset->offset)); matchedFilter = o; break; } } #if 0 // If no filter exists, let's create one if (matchedFilter == nullptr) { KStarsData::Instance()->userdb()->AddFilter(FilterCaptureCombo->currentText(), "", "", - QString::number(oneOffset->offset), oneOffset->filter, "1"); + QString::number(oneOffset->offset), oneOffset->filter, "1"); } // Or update Existing one else { KStarsData::Instance()->userdb()->AddFilter(FilterCaptureCombo->currentText(), "", "", - QString::number(oneOffset->offset), oneOffset->filter, - matchedFilter->exposure(), matchedFilter->id()); + QString::number(oneOffset->offset), oneOffset->filter, + matchedFilter->exposure(), matchedFilter->id()); } #endif } } } #endif void Capture::toggleVideo(bool enabled) { if (currentCCD == nullptr) return; if (currentCCD->isBLOBEnabled() == false) { if (Options::guiderType() != Ekos::Guide::GUIDE_INTERNAL || KMessageBox::questionYesNo(nullptr, i18n("Image transfer is disabled for this camera. Would you like to enable it?")) == KMessageBox::Yes) currentCCD->setBLOBEnabled(true); else return; } currentCCD->setVideoStreamEnabled(enabled); } void Capture::setVideoStreamEnabled(bool enabled) { if (enabled) { liveVideoB->setChecked(true); liveVideoB->setIcon(QIcon::fromTheme("camera-on")); //liveVideoB->setStyleSheet("color:red;"); } else { liveVideoB->setChecked(false); liveVideoB->setIcon(QIcon::fromTheme("camera-ready")); //liveVideoB->setStyleSheet(QString()); } } void Capture::setMountStatus(ISD::Telescope::Status newState) { switch (newState) { - case ISD::Telescope::MOUNT_PARKING: - case ISD::Telescope::MOUNT_SLEWING: - case ISD::Telescope::MOUNT_MOVING: - previewB->setEnabled(false); - liveVideoB->setEnabled(false); - // Only disable when button is "Start", and not "Stopped" - // If mount is in motion, Stopped button should always be enabled to terminate - // the sequence - if (pi->isAnimated() == false) - startB->setEnabled(false); - break; + case ISD::Telescope::MOUNT_PARKING: + case ISD::Telescope::MOUNT_SLEWING: + case ISD::Telescope::MOUNT_MOVING: + previewB->setEnabled(false); + liveVideoB->setEnabled(false); + // Only disable when button is "Start", and not "Stopped" + // If mount is in motion, Stopped button should always be enabled to terminate + // the sequence + if (pi->isAnimated() == false) + startB->setEnabled(false); + break; - default: - if (pi->isAnimated() == false) - { - previewB->setEnabled(true); - if (currentCCD) - liveVideoB->setEnabled(currentCCD->hasVideoStream()); - startB->setEnabled(true); - } + default: + if (pi->isAnimated() == false) + { + previewB->setEnabled(true); + if (currentCCD) + liveVideoB->setEnabled(currentCCD->hasVideoStream()); + startB->setEnabled(true); + } - break; + break; } } void Capture::showObserverDialog() { QList m_observerList; KStars::Instance()->data()->userdb()->GetAllObservers(m_observerList); QStringList observers; for (auto &o : m_observerList) observers << QString("%1 %2").arg(o->name(), o->surname()); QDialog observersDialog(this); observersDialog.setWindowTitle(i18n("Select Current Observer")); QLabel label(i18n("Current Observer:")); QComboBox observerCombo(&observersDialog); observerCombo.addItems(observers); observerCombo.setCurrentText(m_ObserverName); observerCombo.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QPushButton manageObserver(&observersDialog); manageObserver.setFixedSize(QSize(32, 32)); manageObserver.setIcon(QIcon::fromTheme("document-edit")); manageObserver.setAttribute(Qt::WA_LayoutUsesWidgetRect); manageObserver.setToolTip(i18n("Manage Observers")); - connect(&manageObserver, &QPushButton::clicked, this, [&]() { + connect(&manageObserver, &QPushButton::clicked, this, [&]() + { ObserverAdd add; add.exec(); QList m_observerList; KStars::Instance()->data()->userdb()->GetAllObservers(m_observerList); QStringList observers; for (auto &o : m_observerList) observers << QString("%1 %2").arg(o->name(), o->surname()); observerCombo.clear(); observerCombo.addItems(observers); observerCombo.setCurrentText(m_ObserverName); }); - QHBoxLayout *layout = new QHBoxLayout; + QHBoxLayout * layout = new QHBoxLayout; layout->addWidget(&label); layout->addWidget(&observerCombo); layout->addWidget(&manageObserver); observersDialog.setLayout(layout); observersDialog.exec(); m_ObserverName = observerCombo.currentText(); Options::setDefaultObserver(m_ObserverName); } void Capture::startRefocusTimer(bool forced) { /* If refocus is requested, only restart timer if not already running in order to keep current elapsed time since last refocus */ if (refocusEveryNCheck->isChecked()) { // How much time passed since we last started the time uint32_t elapsedSecs = refocusEveryNTimer.elapsed()/1000; // How many seconds do we wait for between focusing (60 mins ==> 3600 secs) uint32_t totalSecs = refocusEveryN->value()*60; if (!refocusEveryNTimer.isValid() || forced) { appendLogText(i18n("Ekos will refocus in %1 seconds.", totalSecs)); refocusEveryNTimer.restart(); } else if (elapsedSecs < totalSecs) { //appendLogText(i18n("Ekos will refocus in %1 seconds, last procedure was %2 seconds ago.", refocusEveryNTimer.elapsed()/1000-refocusEveryNTimer.elapsed()*60, refocusEveryNTimer.elapsed()/1000)); appendLogText(i18n("Ekos will refocus in %1 seconds, last procedure was %2 seconds ago.", totalSecs - elapsedSecs, elapsedSecs)); } else { appendLogText(i18n("Ekos will refocus as soon as possible, last procedure was %1 seconds ago.", elapsedSecs)); } } } int Capture::getRefocusEveryNTimerElapsedSec() { /* If timer isn't valid, consider there is no focus to be done, that is, that focus was just done */ return refocusEveryNTimer.isValid() ? refocusEveryNTimer.elapsed()/1000 : 0; } void Capture::setAlignResults(double orientation, double ra, double de, double pixscale) { Q_UNUSED(orientation); Q_UNUSED(ra); Q_UNUSED(de); Q_UNUSED(pixscale); if (currentRotator == nullptr) return; rotatorSettings->refresh(); } void Capture::setFilterManager(const QSharedPointer &manager) { filterManager = manager; connect(filterManagerB, &QPushButton::clicked, [this]() { filterManager->show(); filterManager->raise(); }); connect(filterManager.data(), &FilterManager::ready, [this]() { m_CurrentFilterPosition = filterManager->getFilterPosition(); // Due to race condition, focusState = FOCUS_IDLE; if (activeJob) activeJob->setCurrentFilter(m_CurrentFilterPosition); } - ); + ); connect(filterManager.data(), &FilterManager::failed, [this]() { if (activeJob) { appendLogText(i18n("Filter operation failed.")); abort(); } } - ); + ); connect(filterManager.data(), &FilterManager::newStatus, [this](Ekos::FilterState filterState) { if (m_State == CAPTURE_CHANGING_FILTER) { secondsLabel->setText(Ekos::getFilterStatusString(filterState)); switch (filterState) { - case FILTER_OFFSET: - appendLogText(i18n("Changing focus offset by %1 steps...", filterManager->getTargetFilterOffset())); - break; + case FILTER_OFFSET: + appendLogText(i18n("Changing focus offset by %1 steps...", filterManager->getTargetFilterOffset())); + break; - case FILTER_CHANGE: - appendLogText(i18n("Changing filter to %1...", FilterPosCombo->itemText(filterManager->getTargetFilterPosition()-1))); - break; + case FILTER_CHANGE: + appendLogText(i18n("Changing filter to %1...", FilterPosCombo->itemText(filterManager->getTargetFilterPosition()-1))); + break; - case FILTER_AUTOFOCUS: - appendLogText(i18n("Auto focus on filter change...")); - clearAutoFocusHFR(); - break; + case FILTER_AUTOFOCUS: + appendLogText(i18n("Auto focus on filter change...")); + clearAutoFocusHFR(); + break; - default: - break; + default: + break; } } }); connect(filterManager.data(), &FilterManager::labelsChanged, this, [this]() { FilterPosCombo->clear(); FilterPosCombo->addItems(filterManager->getFilterLabels()); m_CurrentFilterPosition = filterManager->getFilterPosition(); FilterPosCombo->setCurrentIndex(m_CurrentFilterPosition-1); }); connect(filterManager.data(), &FilterManager::positionChanged, this, [this]() { m_CurrentFilterPosition = filterManager->getFilterPosition(); FilterPosCombo->setCurrentIndex(m_CurrentFilterPosition-1); }); } void Capture::addDSLRInfo(const QString &model, uint32_t maxW, uint32_t maxH, double pixelW, double pixelH) { // Check if model already exists auto pos = std::find_if(DSLRInfos.begin(), DSLRInfos.end(), [model](QMap &oneDSLRInfo) - { return (oneDSLRInfo["Model"] == model);}); + { + return (oneDSLRInfo["Model"] == model); + }); if (pos != DSLRInfos.end()) { KStarsData::Instance()->userdb()->DeleteDSLRInfo(model); DSLRInfos.removeOne(*pos); } QMap oneDSLRInfo; oneDSLRInfo["Model"] = model; oneDSLRInfo["Width"] = maxW; oneDSLRInfo["Height"] = maxH; oneDSLRInfo["PixelW"] = pixelW; oneDSLRInfo["PixelH"] = pixelH; KStarsData::Instance()->userdb()->AddDSLRInfo(oneDSLRInfo); KStarsData::Instance()->userdb()->GetAllDSLRInfos(DSLRInfos); } bool Capture::isModelinDSLRInfo(const QString &model) { auto pos = std::find_if(DSLRInfos.begin(), DSLRInfos.end(), [model](QMap &oneDSLRInfo) - { return (oneDSLRInfo["Model"] == model);}); + { + return (oneDSLRInfo["Model"] == model); + }); return (pos != DSLRInfos.end()); } #if 0 void Capture::syncDriverToDSLRLimits() { if (targetChip == nullptr) return; QString model(currentCCD->getDeviceName()); // Check if model already exists auto pos = std::find_if(DSLRInfos.begin(), DSLRInfos.end(), [model](QMap &oneDSLRInfo) - { return (oneDSLRInfo["Model"] == model);}); + { + return (oneDSLRInfo["Model"] == model); + }); -if (pos != DSLRInfos.end()) -targetChip->setImageInfo((*pos)["Width"].toInt(), (*pos)["Height"].toInt(), (*pos)["PixelW"].toDouble(), (*pos)["PixelH"].toDouble(), 8); + if (pos != DSLRInfos.end()) + targetChip->setImageInfo((*pos)["Width"].toInt(), (*pos)["Height"].toInt(), (*pos)["PixelW"].toDouble(), (*pos)["PixelH"].toDouble(), 8); } #endif void Capture::cullToDSLRLimits() { QString model(currentCCD->getDeviceName()); // Check if model already exists auto pos = std::find_if(DSLRInfos.begin(), DSLRInfos.end(), [model](QMap &oneDSLRInfo) - { return (oneDSLRInfo["Model"] == model);}); - -if (pos != DSLRInfos.end()) -{ - if (frameWIN->maximum() == 0 || frameWIN->maximum() > (*pos)["Width"].toInt()) { - frameWIN->setValue((*pos)["Width"].toInt()); - frameWIN->setMaximum((*pos)["Width"].toInt()); - } + return (oneDSLRInfo["Model"] == model); + }); - if (frameHIN->maximum() == 0 || frameHIN->maximum() > (*pos)["Height"].toInt()) + if (pos != DSLRInfos.end()) { - frameHIN->setValue((*pos)["Height"].toInt()); - frameHIN->setMaximum((*pos)["Height"].toInt()); + if (frameWIN->maximum() == 0 || frameWIN->maximum() > (*pos)["Width"].toInt()) + { + frameWIN->setValue((*pos)["Width"].toInt()); + frameWIN->setMaximum((*pos)["Width"].toInt()); + } + + if (frameHIN->maximum() == 0 || frameHIN->maximum() > (*pos)["Height"].toInt()) + { + frameHIN->setValue((*pos)["Height"].toInt()); + frameHIN->setMaximum((*pos)["Height"].toInt()); + } } } -} void Capture::setCapturedFramesMap(const QString &signature, int count) { capturedFramesMap[signature] = count; qCDebug(KSTARS_EKOS_CAPTURE) << QString("Client module indicates that storage for '%1' has already %2 captures processed.").arg(signature).arg(count); // Scheduler's captured frame map overrides the progress option of the Capture module ignoreJobProgress = false; } void Capture::setSettings(const QJsonObject &settings) { // FIXME: QComboBox signal "activated" does not trigger when setting text programmatically. CCDCaptureCombo->setCurrentText(settings["camera"].toString()); FilterDevicesCombo->setCurrentText(settings["fw"].toString()); FilterPosCombo->setCurrentText(settings["filter"].toString()); exposureIN->setValue(settings["exp"].toDouble(1)); int bin = settings["bin"].toInt(1); setBinning(bin,bin); double temperature = settings["temperature"].toDouble(Ekos::INVALID_VALUE); if (temperature != Ekos::INVALID_VALUE) { setForceTemperature(true); setTargetTemperature(temperature); } double gain = settings["gain"].toDouble(Ekos::INVALID_VALUE); if (gain != Ekos::INVALID_VALUE && currentCCD) { - QMap > customProps = customPropertiesDialog->getCustomProperties(); + QMap > customProps = customPropertiesDialog->getCustomProperties(); - // Gain is manifested in two forms - // Property CCD_GAIN and - // Part of CCD_CONTROLS properties. - // Therefore, we have to find what the currently camera supports first. - if (currentCCD->getProperty("CCD_GAIN")) - { - QMap ccdGain; - ccdGain["GAIN"] = gain; - customProps["CCD_GAIN"] = ccdGain; - } - else if (currentCCD->getProperty("CCD_CONTROLS")) - { - QMap ccdGain; - ccdGain["Gain"] = gain; - customProps["CCD_CONTROLS"] = ccdGain; - } + // Gain is manifested in two forms + // Property CCD_GAIN and + // Part of CCD_CONTROLS properties. + // Therefore, we have to find what the currently camera supports first. + if (currentCCD->getProperty("CCD_GAIN")) + { + QMap ccdGain; + ccdGain["GAIN"] = gain; + customProps["CCD_GAIN"] = ccdGain; + } + else if (currentCCD->getProperty("CCD_CONTROLS")) + { + QMap ccdGain; + ccdGain["Gain"] = gain; + customProps["CCD_CONTROLS"] = ccdGain; + } - customPropertiesDialog->setCustomProperties(customProps); + customPropertiesDialog->setCustomProperties(customProps); } frameTypeCombo->setCurrentIndex(settings["frameType"].toInt(0)); // ISO int isoIndex = settings["iso"].toInt(-1); if (isoIndex >= 0) setISO(isoIndex); } void Capture::clearCameraConfiguration() { if (KMessageBox::questionYesNo(nullptr, i18n("Reset %1 configuration to default?", currentCCD->getDeviceName()), i18n("Confirmation")) == KMessageBox::No) return; currentCCD->setConfig(LOAD_DEFAULT_CONFIG); KStarsData::Instance()->userdb()->DeleteDSLRInfo(currentCCD->getDeviceName()); QStringList shutterfulCCDs = Options::shutterfulCCDs(); QStringList shutterlessCCDs = Options::shutterlessCCDs(); // Remove camera from shutterful and shutterless CCDs if (shutterfulCCDs.contains(currentCCD->getDeviceName())) { shutterfulCCDs.removeOne(currentCCD->getDeviceName()); Options::setShutterfulCCDs(shutterfulCCDs); } if (shutterlessCCDs.contains(currentCCD->getDeviceName())) { shutterlessCCDs.removeOne(currentCCD->getDeviceName()); Options::setShutterlessCCDs(shutterlessCCDs); } // For DSLRs, immediately ask them to enter the values again. if (ISOCombo->count() > 0) { DSLRInfo infoDialog(this, currentCCD); if (infoDialog.exec() == QDialog::Accepted) { addDSLRInfo(QString(currentCCD->getDeviceName()), infoDialog.sensorMaxWidth, infoDialog.sensorMaxHeight, infoDialog.sensorPixelW, infoDialog.sensorPixelH); updateFrameProperties(); resetFrame(); } } } void Capture::setCoolerToggled(bool enabled) { coolerOnB->blockSignals(true); coolerOnB->setChecked(enabled); coolerOnB->blockSignals(false); coolerOffB->blockSignals(true); coolerOffB->setChecked(!enabled); coolerOffB->blockSignals(false); appendLogText(enabled ? i18n("Cooler is on") : i18n("Cooler is off")); } void Capture::processCaptureTimeout() { captureTimeoutCounter++; if (captureTimeoutCounter >= 3) { captureTimeoutCounter = 0; appendLogText(i18n("Exposure timeout. Aborting...")); abort(); return; } appendLogText(i18n("Exposure timeout. Restarting exposure...")); - ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); + ISD::CCDChip * targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); targetChip->abortExposure(); targetChip->capture(exposureIN->value()); captureTimeout.start(exposureIN->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); } } diff --git a/kstars/ekos/capture/sequencejob.h b/kstars/ekos/capture/sequencejob.h index 832dfa322..78e16d7ba 100644 --- a/kstars/ekos/capture/sequencejob.h +++ b/kstars/ekos/capture/sequencejob.h @@ -1,293 +1,413 @@ /* Ekos Capture tool Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "indi/indistd.h" #include "indi/indiccd.h" #include "ekos/auxiliary/filtermanager.h" #include "skyobjects/skypoint.h" #include /** * @class SequenceJob * @short Sequence Job is a container for the details required to capture a series of images. * * @author Jasem Mutlaq * @version 1.0 */ namespace Ekos { class SequenceJob : public QObject { - Q_OBJECT - - public: - typedef enum { JOB_IDLE, JOB_BUSY, JOB_ERROR, JOB_ABORTED, JOB_DONE } JOBStatus; - typedef enum - { - CAPTURE_OK, - CAPTURE_FRAME_ERROR, - CAPTURE_BIN_ERROR, - CAPTURE_FILTER_BUSY, - CAPTURE_FOCUS_ERROR - } CAPTUREResult; - typedef enum - { - ACTION_FILTER, - ACTION_TEMPERATURE, - ACTION_ROTATOR - } PrepareActions; - - static QString const & ISOMarker; - - SequenceJob(); - ~SequenceJob() = default; - - CAPTUREResult capture(bool noCaptureFilter); - void reset(); - void abort(); - void done(); - void prepareCapture(); - - JOBStatus getStatus() { return status; } - const QString &getStatusString() { return statusStrings[status]; } - bool isPreview() { return preview; } - int getDelay() { return delay; } - int getCount() { return count; } - int getCompleted() { return completed; } - const QString &getRawPrefix() { return rawPrefix; } - double getExposure() const { return exposure; } - - void setActiveCCD(ISD::CCD *ccd) { activeCCD = ccd; } - ISD::CCD *getActiveCCD() { return activeCCD; } - - void setActiveFilter(ISD::GDInterface *filter) { activeFilter = filter; } - ISD::GDInterface *getActiveFilter() { return activeFilter; } - - void setActiveRotator(ISD::GDInterface *rotator) { activeRotator = rotator; } - ISD::GDInterface *getActiveRotator() { return activeRotator; } - - void setActiveChip(ISD::CCDChip *chip) { activeChip = chip; } - ISD::CCDChip *getActiveChip() { return activeChip; } - - void setLocalDir(const QString &dir) { localDirectory = dir; } - const QString &getLocalDir() { return localDirectory; } - QString getSignature() { return QString(getLocalDir() + getDirectoryPostfix() + '/' + getFullPrefix()).remove(ISOMarker); } - - void setTargetFilter(int pos, const QString &name); - int getTargetFilter() { return targetFilter; } - int getCurrentFilter() const; - void setCurrentFilter(int value); - - const QString &getFilterName() { return filter; } - - void setFrameType(CCDFrameType type); - CCDFrameType getFrameType() { return frameType; } - - void setFilterManager(const QSharedPointer &manager) { filterManager = manager; } - - void setCaptureFilter(FITSScale capFilter) { captureFilter = capFilter; } - FITSScale getCaptureFilter() { return captureFilter; } - - void setPreview(bool enable) { preview = enable; } - void setFullPrefix(const QString &cprefix) { fullPrefix = cprefix; } - const QString &getFullPrefix() { return fullPrefix; } - void setFrame(int in_x, int in_y, int in_w, int in_h) - { - x = in_x; - y = in_y; - w = in_w; - h = in_h; - } - - int getSubX() { return x; } - int getSubY() { return y; } - int getSubW() { return w; } - int getSubH() { return h; } - - void setBin(int xbin, int ybin) - { - binX = xbin; - binY = ybin; - } - int getXBin() { return binX; } - int getYBin() { return binY; } - - void setDelay(int in_delay) { delay = in_delay; } - void setCount(int in_count); - void setExposure(double duration) { exposure = duration; } - void setStatusCell(QTableWidgetItem *cell); - void setCountCell(QTableWidgetItem *cell); - void setCompleted(int in_completed); - int getISOIndex() const; - void setISOIndex(int value); - - double getExposeLeft() const; - void setExposeLeft(double value); - void resetStatus(); - - void setPrefixSettings(const QString &rawFilePrefix, bool filterEnabled, bool exposureEnabled, bool tsEnabled); - void getPrefixSettings(QString &rawFilePrefix, bool &filterEnabled, bool &exposureEnabled, bool &tsEnabled); - - bool isFilterPrefixEnabled() { return filterPrefixEnabled; } - bool isExposurePrefixEnabled() { return expPrefixEnabled; } - bool isTimestampPrefixEnabled() { return timeStampPrefixEnabled; } - - double getCurrentTemperature() const; - void setCurrentTemperature(double value); - - double getTargetTemperature() const; - void setTargetTemperature(double value); - - double getTargetADU() const; - void setTargetADU(double value); - - double getTargetADUTolerance() const; - void setTargetADUTolerance(double value); - - int getCaptureRetires() const; - void setCaptureRetires(int value); - - FlatFieldSource getFlatFieldSource() const; - void setFlatFieldSource(const FlatFieldSource &value); - - FlatFieldDuration getFlatFieldDuration() const; - void setFlatFieldDuration(const FlatFieldDuration &value); - - SkyPoint getWallCoord() const; - void setWallCoord(const SkyPoint &value); - - bool isPreMountPark() const; - void setPreMountPark(bool value); - - bool isPreDomePark() const; - void setPreDomePark(bool value); - - bool getEnforceTemperature() const; - void setEnforceTemperature(bool value); - - QString getPostCaptureScript() const; - void setPostCaptureScript(const QString &value); - - ISD::CCD::UploadMode getUploadMode() const; - void setUploadMode(const ISD::CCD::UploadMode &value); - - QString getRemoteDir() const; - void setRemoteDir(const QString &value); - - ISD::CCD::TransferFormat getTransforFormat() const; - void setTransforFormat(const ISD::CCD::TransferFormat &value); - - double getGain() const; - void setGain(double value); - - double getTargetRotation() const; - void setTargetRotation(double value); - - void setCurrentRotation(double value); - - QMap > getCustomProperties() const; - void setCustomProperties(const QMap > &value); - - QString getDirectoryPostfix() const; - void setDirectoryPostfix(const QString &value); - - bool getJobProgressIgnored() const; - void setJobProgressIgnored(bool JobProgressIgnored); - -signals: - void prepareComplete(); - void prepareState(Ekos::CaptureState state); - void checkFocus(); - -private: - bool areActionsReady(); - void setAllActionsReady(); - void setStatus(JOBStatus const); - - QStringList statusStrings; - ISD::CCDChip *activeChip { nullptr }; - ISD::CCD *activeCCD { nullptr }; - ISD::GDInterface *activeFilter { nullptr }; - ISD::GDInterface *activeRotator { nullptr }; - - double exposure { -1 }; - CCDFrameType frameType { FRAME_LIGHT }; - int targetFilter { -1 }; - int currentFilter { 0 }; - - QString filter; - int binX { 0 }; - int binY { 0 }; - int x { 0 }; - int y { 0 }; - int w { 0 }; - int h { 0 }; - QString fullPrefix; - int count { -1 }; - int delay { -1 }; - bool preview { false }; - bool prepareReady { true }; - bool enforceTemperature { false }; - bool m_JobProgressIgnored { false }; - int isoIndex { -1 }; - int captureRetires { 0 }; - unsigned int completed { 0 }; - double exposeLeft { 0 }; - double currentTemperature { 0 }; - double targetTemperature { 0 }; - double gain { -1 }; - // Rotation in absolute ticks, NOT angle - double targetRotation { 0 }; - double currentRotation { 0 }; - FITSScale captureFilter { FITS_NONE }; - QTableWidgetItem *statusCell { nullptr }; - QTableWidgetItem *countCell { nullptr }; - QString postCaptureScript; - - ISD::CCD::UploadMode uploadMode { ISD::CCD::UPLOAD_CLIENT }; - - // Transfer Format - ISD::CCD::TransferFormat transforFormat { ISD::CCD::FORMAT_FITS }; - - // Directory Settings - QString localDirectory; - QString remoteDirectory; - QString directoryPostfix; - - bool filterPrefixEnabled { false }; - bool expPrefixEnabled { false }; - bool timeStampPrefixEnabled { false }; - QString rawPrefix; - - JOBStatus status { JOB_IDLE }; - - // Flat field variables - struct - { - double targetADU { 0 }; - double targetADUTolerance { 250 }; - FlatFieldSource flatFieldSource { SOURCE_MANUAL }; - FlatFieldDuration flatFieldDuration { DURATION_MANUAL }; - SkyPoint wallCoord; - bool preMountPark { false }; - bool preDomePark { false }; - - } calibrationSettings; - - QMap prepareActions; - - QMap> customProperties; - - // Filter Manager - QSharedPointer filterManager; + Q_OBJECT + Q_PROPERTY(QString filename MEMBER m_filename) + + public: + typedef enum { JOB_IDLE, JOB_BUSY, JOB_ERROR, JOB_ABORTED, JOB_DONE } JOBStatus; + typedef enum + { + CAPTURE_OK, + CAPTURE_FRAME_ERROR, + CAPTURE_BIN_ERROR, + CAPTURE_FILTER_BUSY, + CAPTURE_FOCUS_ERROR + } CAPTUREResult; + typedef enum + { + ACTION_FILTER, + ACTION_TEMPERATURE, + ACTION_ROTATOR + } PrepareActions; + + static QString const &ISOMarker; + + SequenceJob(); + ~SequenceJob() = default; + + CAPTUREResult capture(bool noCaptureFilter); + void reset(); + void abort(); + void done(); + void prepareCapture(); + + JOBStatus getStatus() + { + return status; + } + const QString &getStatusString() + { + return statusStrings[status]; + } + bool isPreview() + { + return preview; + } + int getDelay() + { + return delay; + } + int getCount() + { + return count; + } + int getCompleted() + { + return completed; + } + const QString &getRawPrefix() + { + return rawPrefix; + } + double getExposure() const + { + return exposure; + } + + void setActiveCCD(ISD::CCD * ccd) + { + activeCCD = ccd; + } + ISD::CCD * getActiveCCD() + { + return activeCCD; + } + + void setActiveFilter(ISD::GDInterface * filter) + { + activeFilter = filter; + } + ISD::GDInterface * getActiveFilter() + { + return activeFilter; + } + + void setActiveRotator(ISD::GDInterface * rotator) + { + activeRotator = rotator; + } + ISD::GDInterface * getActiveRotator() + { + return activeRotator; + } + + void setActiveChip(ISD::CCDChip * chip) + { + activeChip = chip; + } + ISD::CCDChip * getActiveChip() + { + return activeChip; + } + + void setLocalDir(const QString &dir) + { + localDirectory = dir; + } + const QString &getLocalDir() + { + return localDirectory; + } + QString getSignature() + { + return QString(getLocalDir() + getDirectoryPostfix() + '/' + getFullPrefix()).remove(ISOMarker); + } + + void setTargetFilter(int pos, const QString &name); + int getTargetFilter() + { + return targetFilter; + } + int getCurrentFilter() const; + void setCurrentFilter(int value); + + const QString &getFilterName() + { + return filter; + } + + void setFrameType(CCDFrameType type); + CCDFrameType getFrameType() + { + return frameType; + } + + void setFilterManager(const QSharedPointer &manager) + { + filterManager = manager; + } + + void setCaptureFilter(FITSScale capFilter) + { + captureFilter = capFilter; + } + FITSScale getCaptureFilter() + { + return captureFilter; + } + + void setPreview(bool enable) + { + preview = enable; + } + void setFullPrefix(const QString &cprefix) + { + fullPrefix = cprefix; + } + const QString &getFullPrefix() + { + return fullPrefix; + } + void setFrame(int in_x, int in_y, int in_w, int in_h) + { + x = in_x; + y = in_y; + w = in_w; + h = in_h; + } + + int getSubX() + { + return x; + } + int getSubY() + { + return y; + } + int getSubW() + { + return w; + } + int getSubH() + { + return h; + } + + void setBin(int xbin, int ybin) + { + binX = xbin; + binY = ybin; + } + int getXBin() + { + return binX; + } + int getYBin() + { + return binY; + } + + void setDelay(int in_delay) + { + delay = in_delay; + } + void setCount(int in_count); + void setExposure(double duration) + { + exposure = duration; + } + void setStatusCell(QTableWidgetItem * cell); + void setCountCell(QTableWidgetItem * cell); + void setCompleted(int in_completed); + int getISOIndex() const; + void setISOIndex(int value); + + double getExposeLeft() const; + void setExposeLeft(double value); + void resetStatus(); + + void setPrefixSettings(const QString &rawFilePrefix, bool filterEnabled, bool exposureEnabled, bool tsEnabled); + void getPrefixSettings(QString &rawFilePrefix, bool &filterEnabled, bool &exposureEnabled, bool &tsEnabled); + + bool isFilterPrefixEnabled() + { + return filterPrefixEnabled; + } + bool isExposurePrefixEnabled() + { + return expPrefixEnabled; + } + bool isTimestampPrefixEnabled() + { + return timeStampPrefixEnabled; + } + + double getCurrentTemperature() const; + void setCurrentTemperature(double value); + + double getTargetTemperature() const; + void setTargetTemperature(double value); + + double getTargetADU() const; + void setTargetADU(double value); + + double getTargetADUTolerance() const; + void setTargetADUTolerance(double value); + + int getCaptureRetires() const; + void setCaptureRetires(int value); + + FlatFieldSource getFlatFieldSource() const; + void setFlatFieldSource(const FlatFieldSource &value); + + FlatFieldDuration getFlatFieldDuration() const; + void setFlatFieldDuration(const FlatFieldDuration &value); + + SkyPoint getWallCoord() const; + void setWallCoord(const SkyPoint &value); + + bool isPreMountPark() const; + void setPreMountPark(bool value); + + bool isPreDomePark() const; + void setPreDomePark(bool value); + + bool getEnforceTemperature() const; + void setEnforceTemperature(bool value); + + QString getPostCaptureScript() const; + void setPostCaptureScript(const QString &value); + + ISD::CCD::UploadMode getUploadMode() const; + void setUploadMode(const ISD::CCD::UploadMode &value); + + QString getRemoteDir() const; + void setRemoteDir(const QString &value); + + ISD::CCD::TransferFormat getTransforFormat() const; + void setTransforFormat(const ISD::CCD::TransferFormat &value); + + double getGain() const; + void setGain(double value); + + double getTargetRotation() const; + void setTargetRotation(double value); + + void setCurrentRotation(double value); + + QMap > getCustomProperties() const; + void setCustomProperties(const QMap > &value); + + QString getDirectoryPostfix() const; + void setDirectoryPostfix(const QString &value); + + bool getJobProgressIgnored() const; + void setJobProgressIgnored(bool JobProgressIgnored); + + signals: + void prepareComplete(); + void prepareState(Ekos::CaptureState state); + void checkFocus(); + + private: + bool areActionsReady(); + void setAllActionsReady(); + void setStatus(JOBStatus const); + + QStringList statusStrings; + ISD::CCDChip * activeChip { nullptr }; + ISD::CCD * activeCCD { nullptr }; + ISD::GDInterface * activeFilter { nullptr }; + ISD::GDInterface * activeRotator { nullptr }; + + double exposure { -1 }; + CCDFrameType frameType { FRAME_LIGHT }; + int targetFilter { -1 }; + int currentFilter { 0 }; + + QString filter; + int binX { 0 }; + int binY { 0 }; + int x { 0 }; + int y { 0 }; + int w { 0 }; + int h { 0 }; + QString fullPrefix; + int count { -1 }; + int delay { -1 }; + bool preview { false }; + bool prepareReady { true }; + bool enforceTemperature { false }; + bool m_JobProgressIgnored { false }; + int isoIndex { -1 }; + int captureRetires { 0 }; + unsigned int completed { 0 }; + double exposeLeft { 0 }; + double currentTemperature { 0 }; + double targetTemperature { 0 }; + double gain { -1 }; + // Rotation in absolute ticks, NOT angle + double targetRotation { 0 }; + double currentRotation { 0 }; + FITSScale captureFilter { FITS_NONE }; + QTableWidgetItem * statusCell { nullptr }; + QTableWidgetItem * countCell { nullptr }; + QString postCaptureScript; + + ISD::CCD::UploadMode uploadMode { ISD::CCD::UPLOAD_CLIENT }; + + // Transfer Format + ISD::CCD::TransferFormat transforFormat { ISD::CCD::FORMAT_FITS }; + + // Directory Settings + QString localDirectory; + QString remoteDirectory; + QString directoryPostfix; + + bool filterPrefixEnabled { false }; + bool expPrefixEnabled { false }; + bool timeStampPrefixEnabled { false }; + QString rawPrefix; + + QString m_filename; + + JOBStatus status { JOB_IDLE }; + + // Flat field variables + struct + { + double targetADU { 0 }; + double targetADUTolerance { 250 }; + FlatFieldSource flatFieldSource { SOURCE_MANUAL }; + FlatFieldDuration flatFieldDuration { DURATION_MANUAL }; + SkyPoint wallCoord; + bool preMountPark { false }; + bool preDomePark { false }; + + } calibrationSettings; + + QMap prepareActions; + + QMap> customProperties; + + // Filter Manager + QSharedPointer filterManager; }; } diff --git a/kstars/ekos/ekoslive/cloud.cpp b/kstars/ekos/ekoslive/cloud.cpp index 88f4c3e24..1d58b4db0 100644 --- a/kstars/ekos/ekoslive/cloud.cpp +++ b/kstars/ekos/ekoslive/cloud.cpp @@ -1,194 +1,209 @@ /* Ekos Live Cloud Copyright (C) 2018 Jasem Mutlaq Cloud Channel This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "cloud.h" #include "commands.h" #include "profileinfo.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fpack.h" #include "ekos_debug.h" +#include +#include #include namespace EkosLive { -Cloud::Cloud(Ekos::Manager *manager): m_Manager(manager) +Cloud::Cloud(Ekos::Manager * manager): m_Manager(manager) { connect(&m_WebSocket, &QWebSocket::connected, this, &Cloud::onConnected); connect(&m_WebSocket, &QWebSocket::disconnected, this, &Cloud::onDisconnected); connect(&m_WebSocket, static_cast(&QWebSocket::error), this, &Cloud::onError); } void Cloud::connectServer() { QUrl requestURL(m_URL); QString token = m_AuthResponse.contains("remoteToken") ? m_AuthResponse["remoteToken"].toString() - : m_AuthResponse["token"].toString(); + : m_AuthResponse["token"].toString(); QUrlQuery query; query.addQueryItem("username", m_AuthResponse["username"].toString()); query.addQueryItem("token", token); query.addQueryItem("email", m_AuthResponse["email"].toString()); query.addQueryItem("from_date", m_AuthResponse["from_date"].toString()); query.addQueryItem("to_date", m_AuthResponse["to_date"].toString()); query.addQueryItem("plan_id", m_AuthResponse["plan_id"].toString()); query.addQueryItem("type", m_AuthResponse["type"].toString()); requestURL.setPath("/cloud/ekos"); requestURL.setQuery(query); m_WebSocket.open(requestURL); qCInfo(KSTARS_EKOS) << "Connecting to cloud websocket server at" << requestURL.toDisplayString(); } void Cloud::disconnectServer() { m_WebSocket.close(); } void Cloud::onConnected() { qCInfo(KSTARS_EKOS) << "Connected to Cloud Websocket server at" << m_URL.toDisplayString(); connect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Cloud::onTextReceived); m_isConnected = true; m_ReconnectTries=0; emit connected(); } void Cloud::onDisconnected() { qCInfo(KSTARS_EKOS) << "Disonnected from Cloud Websocket server."; m_isConnected = false; disconnect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Cloud::onTextReceived); m_sendBlobs = true; for (const QString &oneFile : temporaryFiles) QFile::remove(oneFile); temporaryFiles.clear(); emit disconnected(); } void Cloud::onError(QAbstractSocket::SocketError error) { qCritical(KSTARS_EKOS) << "Cloud Websocket connection error" << m_WebSocket.errorString(); if (error == QAbstractSocket::RemoteHostClosedError || error == QAbstractSocket::ConnectionRefusedError) { if (m_ReconnectTries++ < RECONNECT_MAX_TRIES) QTimer::singleShot(RECONNECT_INTERVAL, this, SLOT(connectServer())); } } void Cloud::onTextReceived(const QString &message) { qCInfo(KSTARS_EKOS) << "Cloud Text Websocket Message" << message; QJsonParseError error; auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString(); return; } - const QJsonObject msgObj = serverMessage.object(); - const QString command = msgObj["type"].toString(); + const QJsonObject msgObj = serverMessage.object(); + const QString command = msgObj["type"].toString(); // const QJsonObject payload = msgObj["payload"].toObject(); // if (command == commands[ALIGN_SET_FILE_EXTENSION]) // extension = payload["ext"].toString(); if (command == commands[SET_BLOBS]) - m_sendBlobs = msgObj["payload"].toBool(); + m_sendBlobs = msgObj["payload"].toBool(); else if (command == commands[LOGOUT]) disconnectServer(); } -void Cloud::sendPreviewImage(FITSView *view, const QString &uuid) +void Cloud::sendPreviewImage(const QString &filename, const QString &uuid) { - const FITSData *imageData = view->getImageData(); - - if (m_isConnected == false || m_Options[OPTION_SET_CLOUD_STORAGE] == false - || m_sendBlobs == false - || imageData->isTempFile()) + if (m_isConnected == false || m_Options[OPTION_SET_CLOUD_STORAGE] == false || m_sendBlobs == false) return; + m_UUID = uuid; + + imageData.reset(new FITSData()); + QFuture result = imageData->loadFITS(filename); + watcher.setFuture(result); + connect(&watcher, &QFutureWatcher::finished, this, &Cloud::sendImage); +} + +void Cloud::sendImage() +{ + QtConcurrent::run(this, &Cloud::asyncUpload); +} + +void Cloud::asyncUpload() +{ // Send complete metadata // Add file name and size QJsonObject metadata; // Skip empty or useless metadata - for (FITSData::Record *oneRecord : imageData->getRecords()) + for (FITSData::Record * oneRecord : imageData->getRecords()) { if (oneRecord->key == "EXTEND" || oneRecord->key == "SIMPLE" || oneRecord->key == "COMMENT" || - oneRecord->key.isEmpty() || oneRecord->value.toString().isEmpty()) + oneRecord->key.isEmpty() || oneRecord->value.toString().isEmpty()) continue; metadata.insert(oneRecord->key.toLower(), QJsonValue::fromVariant(oneRecord->value)); } // Filename only without path QString filename = QFileInfo(imageData->filename()).fileName(); // Add filename and size as wells - metadata.insert("uuid", uuid); + metadata.insert("uuid", m_UUID); metadata.insert("filename", filename); metadata.insert("filesize", static_cast(imageData->size())); // Must set Content-Disposition so metadata.insert("Content-Disposition", QString("attachment;filename=%1.fz").arg(filename)); m_WebSocket.sendTextMessage(QJsonDocument(metadata).toJson(QJsonDocument::Compact)); qCInfo(KSTARS_EKOS) << "Uploading file to the cloud with metadata" << metadata; // Use cfitsio pack to compress the file first - filename = QDir::tempPath() + QString("/ekoslivecloud%1").arg(uuid); + filename = QDir::tempPath() + QString("/ekoslivecloud%1").arg(m_UUID); fpstate fpvar; std::vector arguments = {"fpack", imageData->filename().toLatin1().data()}; std::vector arglist; - for (const auto& arg : arguments) - arglist.push_back((char*)arg.data()); + for (const auto &arg : arguments) + arglist.push_back((char *)arg.data()); arglist.push_back(nullptr); int argc = arglist.size() - 1; - char **argv = arglist.data(); + char ** argv = arglist.data(); // TODO: Check for errors fp_init (&fpvar); fp_get_param (argc, argv, &fpvar); fp_preflight (argc, argv, FPACK, &fpvar); fp_loop (argc, argv, FPACK, filename.toLatin1().data(), fpvar); QString newCompressedFile = filename + ".fz"; // Upload the compressed image QFile image(newCompressedFile); if (image.open(QIODevice::ReadOnly)) { m_WebSocket.sendBinaryMessage(image.readAll()); qCInfo(KSTARS_EKOS) << "Uploaded" << newCompressedFile << " to the cloud"; } image.close(); // Remove from disk QFile::remove(newCompressedFile); + + imageData.reset(); } } diff --git a/kstars/ekos/ekoslive/cloud.h b/kstars/ekos/ekoslive/cloud.h index 8914440f5..1a8338044 100644 --- a/kstars/ekos/ekoslive/cloud.h +++ b/kstars/ekos/ekoslive/cloud.h @@ -1,89 +1,107 @@ /* Ekos Live Client Copyright (C) 2018 Jasem Mutlaq Cloud Channel This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include #include #include "ekos/ekos.h" #include "ekos/manager.h" class FITSView; namespace EkosLive { class Cloud : public QObject { - Q_OBJECT - -public: - Cloud(Ekos::Manager *manager); - virtual ~Cloud() = default; - - void sendResponse(const QString &command, const QJsonObject &payload); - void sendResponse(const QString &command, const QJsonArray &payload); - - void setAuthResponse(const QJsonObject &response) {m_AuthResponse = response;} - void setURL(const QUrl &url) {m_URL = url;} - - void registerCameras(); - - // Ekos Cloud Message to User - void sendPreviewImage(FITSView *view, const QString &uuid); - -signals: - void connected(); - void disconnected(); - -public slots: - void connectServer(); - void disconnectServer(); - void setOptions(QMap options) {m_Options = options;} - -private slots: - // Connection - void onConnected(); - void onDisconnected(); - void onError(QAbstractSocket::SocketError error); - - // Communication - void onTextReceived(const QString &message); - -private: - QWebSocket m_WebSocket; - QJsonObject m_AuthResponse; - uint16_t m_ReconnectTries {0}; - Ekos::Manager *m_Manager { nullptr }; - QUrl m_URL; - - QString extension; - QStringList temporaryFiles; - - bool m_isConnected {false}; - bool m_sendBlobs {true}; - - QMap m_Options; - - // Image width for high-bandwidth setting - static const uint16_t HB_WIDTH = 640; - // Image high bandwidth image quality (jpg) - static const uint8_t HB_IMAGE_QUALITY = 76; - // Video high bandwidth video quality (jpg) - static const uint8_t HB_VIDEO_QUALITY = 64; - - // Retry every 5 seconds in case remote server is down - static const uint16_t RECONNECT_INTERVAL = 5000; - // Retry for 1 hour before giving up - static const uint16_t RECONNECT_MAX_TRIES = 720; + Q_OBJECT + + public: + Cloud(Ekos::Manager * manager); + virtual ~Cloud() = default; + + void sendResponse(const QString &command, const QJsonObject &payload); + void sendResponse(const QString &command, const QJsonArray &payload); + + void setAuthResponse(const QJsonObject &response) + { + m_AuthResponse = response; + } + void setURL(const QUrl &url) + { + m_URL = url; + } + + void registerCameras(); + + // Ekos Cloud Message to User + void sendPreviewImage(const QString &filename, const QString &uuid); + + signals: + void connected(); + void disconnected(); + + public slots: + void connectServer(); + void disconnectServer(); + void setOptions(QMap options) + { + m_Options = options; + } + + private slots: + // Connection + void onConnected(); + void onDisconnected(); + void onError(QAbstractSocket::SocketError error); + + // Communication + void onTextReceived(const QString &message); + + // Send image + void sendImage(); + + private: + void asyncUpload(); + + QWebSocket m_WebSocket; + QJsonObject m_AuthResponse; + uint16_t m_ReconnectTries {0}; + Ekos::Manager * m_Manager { nullptr }; + QUrl m_URL; + QString m_UUID; + + std::unique_ptr imageData; + QFutureWatcher watcher; + + QString extension; + QStringList temporaryFiles; + + bool m_isConnected {false}; + bool m_sendBlobs {true}; + + QMap m_Options; + + // Image width for high-bandwidth setting + static const uint16_t HB_WIDTH = 640; + // Image high bandwidth image quality (jpg) + static const uint8_t HB_IMAGE_QUALITY = 76; + // Video high bandwidth video quality (jpg) + static const uint8_t HB_VIDEO_QUALITY = 64; + + // Retry every 5 seconds in case remote server is down + static const uint16_t RECONNECT_INTERVAL = 5000; + // Retry for 1 hour before giving up + static const uint16_t RECONNECT_MAX_TRIES = 720; }; } diff --git a/kstars/ekos/ekoslive/media.cpp b/kstars/ekos/ekoslive/media.cpp index d76fd10d1..c2f891622 100644 --- a/kstars/ekos/ekoslive/media.cpp +++ b/kstars/ekos/ekoslive/media.cpp @@ -1,250 +1,279 @@ /* Ekos Live Media Copyright (C) 2018 Jasem Mutlaq Media Channel This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "media.h" #include "commands.h" #include "profileinfo.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsdata.h" #include "ekos_debug.h" +#include #include namespace EkosLive { -Media::Media(Ekos::Manager *manager): m_Manager(manager) +Media::Media(Ekos::Manager * manager): m_Manager(manager) { connect(&m_WebSocket, &QWebSocket::connected, this, &Media::onConnected); connect(&m_WebSocket, &QWebSocket::disconnected, this, &Media::onDisconnected); connect(&m_WebSocket, static_cast(&QWebSocket::error), this, &Media::onError); } void Media::connectServer() { QUrl requestURL(m_URL); QUrlQuery query; query.addQueryItem("username", m_AuthResponse["username"].toString()); query.addQueryItem("token", m_AuthResponse["token"].toString()); if (m_AuthResponse.contains("remoteToken")) query.addQueryItem("remoteToken", m_AuthResponse["remoteToken"].toString()); query.addQueryItem("email", m_AuthResponse["email"].toString()); query.addQueryItem("from_date", m_AuthResponse["from_date"].toString()); query.addQueryItem("to_date", m_AuthResponse["to_date"].toString()); query.addQueryItem("plan_id", m_AuthResponse["plan_id"].toString()); query.addQueryItem("type", m_AuthResponse["type"].toString()); requestURL.setPath("/media/ekos"); requestURL.setQuery(query); m_WebSocket.open(requestURL); qCInfo(KSTARS_EKOS) << "Connecting to Websocket server at" << requestURL.toDisplayString(); } void Media::disconnectServer() { m_WebSocket.close(); } void Media::onConnected() { qCInfo(KSTARS_EKOS) << "Connected to media Websocket server at" << m_URL.toDisplayString(); connect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Media::onTextReceived); connect(&m_WebSocket, &QWebSocket::binaryMessageReceived, this, &Media::onBinaryReceived); m_isConnected = true; m_ReconnectTries=0; emit connected(); } void Media::onDisconnected() { qCInfo(KSTARS_EKOS) << "Disonnected from media Websocket server."; m_isConnected = false; disconnect(&m_WebSocket, &QWebSocket::textMessageReceived, this, &Media::onTextReceived); disconnect(&m_WebSocket, &QWebSocket::binaryMessageReceived, this, &Media::onBinaryReceived); m_sendBlobs = true; for (const QString &oneFile : temporaryFiles) QFile::remove(oneFile); temporaryFiles.clear(); emit disconnected(); } void Media::onError(QAbstractSocket::SocketError error) { qCritical(KSTARS_EKOS) << "Media Websocket connection error" << m_WebSocket.errorString(); if (error == QAbstractSocket::RemoteHostClosedError || - error == QAbstractSocket::ConnectionRefusedError) + error == QAbstractSocket::ConnectionRefusedError) { if (m_ReconnectTries++ < RECONNECT_MAX_TRIES) QTimer::singleShot(RECONNECT_INTERVAL, this, SLOT(connectServer())); } } void Media::onTextReceived(const QString &message) { qCInfo(KSTARS_EKOS) << "Media Text Websocket Message" << message; QJsonParseError error; auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString(); return; } const QJsonObject msgObj = serverMessage.object(); const QString command = msgObj["type"].toString(); const QJsonObject payload = msgObj["payload"].toObject(); if (command == commands[ALIGN_SET_FILE_EXTENSION]) extension = payload["ext"].toString(); else if (command == commands[SET_BLOBS]) m_sendBlobs = msgObj["payload"].toBool(); } void Media::onBinaryReceived(const QByteArray &message) { // For now, we are only receiving binary image (jpg or FITS) for load and slew QTemporaryFile file(QString("/tmp/XXXXXX.%1").arg(extension)); file.setAutoRemove(false); file.open(); file.write(message); file.close(); - Ekos::Align *align = m_Manager->alignModule(); + Ekos::Align * align = m_Manager->alignModule(); const QString filename = file.fileName(); temporaryFiles << filename; align->loadAndSlew(filename); } -void Media::sendPreviewImage(FITSView *view, const QString &uuid) +void Media::sendPreviewImage(const QString &filename, const QString &uuid) { - if (m_isConnected == false || m_Options[OPTION_SET_IMAGE_TRANSFER] == false || m_sendBlobs == false || view == nullptr) + if (m_isConnected == false || m_Options[OPTION_SET_IMAGE_TRANSFER] == false || m_sendBlobs == false) return; + m_UUID = uuid; + + previewImage.reset(new FITSView()); + connect(previewImage.get(), &FITSView::loaded, this, &Media::sendImage); + previewImage->loadFITS(filename); +} + +void Media::sendPreviewImage(FITSView * view, const QString &uuid) +{ + if (m_isConnected == false || m_Options[OPTION_SET_IMAGE_TRANSFER] == false || m_sendBlobs == false) + return; + + m_UUID = uuid; + + upload(view); +} + +void Media::sendImage() +{ + QtConcurrent::run(this, &Media::upload, previewImage.get()); +} + +void Media::upload(FITSView * view) +{ QByteArray jpegData; QBuffer buffer(&jpegData); buffer.open(QIODevice::WriteOnly); QImage scaledImage = view->getDisplayImage().scaledToWidth(m_Options[OPTION_SET_HIGH_BANDWIDTH] ? HB_WIDTH : HB_WIDTH/2); scaledImage.save(&buffer, "jpg", m_Options[OPTION_SET_HIGH_BANDWIDTH] ? HB_IMAGE_QUALITY : HB_IMAGE_QUALITY/2); buffer.close(); - const FITSData *imageData = view->getImageData(); + const FITSData * imageData = view->getImageData(); QString resolution = QString("%1x%2").arg(imageData->width()).arg(imageData->height()); QString sizeBytes = KFormat().formatByteSize(imageData->size()); QVariant xbin(1), ybin(1); imageData->getRecordValue("XBINNING", xbin); imageData->getRecordValue("YBINNING", ybin); QString binning = QString("%1x%2").arg(xbin.toString()).arg(ybin.toString()); QString bitDepth = QString::number(imageData->bpp()); - QJsonObject metadata = { + QJsonObject metadata = + { {"resolution",resolution}, {"size",sizeBytes}, {"bin",binning}, {"bpp",bitDepth}, - {"uuid", imageData->isTempFile() ? "" : uuid}, + {"uuid", imageData->isTempFile() ? "" : m_UUID}, }; m_WebSocket.sendTextMessage(QJsonDocument(metadata).toJson(QJsonDocument::Compact)); m_WebSocket.sendBinaryMessage(jpegData); + + if (view == previewImage.get()) + previewImage.reset(); } -void Media::sendUpdatedFrame(FITSView *view) +void Media::sendUpdatedFrame(FITSView * view) { if (m_isConnected == false || m_Options[OPTION_SET_HIGH_BANDWIDTH] == false || m_sendBlobs == false) return; QByteArray jpegData; QBuffer buffer(&jpegData); buffer.open(QIODevice::WriteOnly); QPixmap displayPixmap = view->getDisplayPixmap(); if (correctionVector.isNull() == false) { QPointF center = 0.5 * correctionVector.p1() + 0.5 * correctionVector.p2(); double length = correctionVector.length(); if (length < 100) length = 100; QRect boundingRectable; boundingRectable.setSize(QSize(static_cast(length*2), static_cast(length*2))); QPoint topLeft = (center - QPointF(length, length)).toPoint(); boundingRectable.moveTo(topLeft); boundingRectable = boundingRectable.intersected(displayPixmap.rect()); emit newBoundingRect(boundingRectable, displayPixmap.size()); displayPixmap = displayPixmap.copy(boundingRectable); } else emit newBoundingRect(QRect(), QSize()); displayPixmap.save(&buffer, "jpg", m_Options[OPTION_SET_HIGH_BANDWIDTH] ? HB_PAH_IMAGE_QUALITY : HB_PAH_IMAGE_QUALITY/2); buffer.close(); m_WebSocket.sendBinaryMessage(jpegData); } -void Media::sendVideoFrame(std::unique_ptr & frame) +void Media::sendVideoFrame(std::unique_ptr &frame) { if (m_isConnected == false || m_Options[OPTION_SET_IMAGE_TRANSFER] == false || m_sendBlobs == false || !frame) return; // TODO Scale should be configurable QImage scaledImage = frame.get()->scaledToWidth(m_Options[OPTION_SET_HIGH_BANDWIDTH] ? HB_WIDTH : HB_WIDTH/2); QTemporaryFile jpegFile; jpegFile.open(); jpegFile.close(); // TODO Quality should be configurable scaledImage.save(jpegFile.fileName(), "jpg", m_Options[OPTION_SET_HIGH_BANDWIDTH] ? HB_VIDEO_QUALITY : HB_VIDEO_QUALITY/2); jpegFile.open(); m_WebSocket.sendBinaryMessage(jpegFile.readAll()); } void Media::registerCameras() { if (m_isConnected == false) return; - for(ISD::GDInterface *gd : m_Manager->findDevices(KSTARS_CCD)) + for(ISD::GDInterface * gd : m_Manager->findDevices(KSTARS_CCD)) { - ISD::CCD *oneCCD = dynamic_cast(gd); + ISD::CCD * oneCCD = dynamic_cast(gd); connect(oneCCD, &ISD::CCD::newVideoFrame, this, &Media::sendVideoFrame, Qt::UniqueConnection); } } void Media::resetPolarView() { this->correctionVector = QLineF(); m_Manager->alignModule()->zoomAlignView(); } } diff --git a/kstars/ekos/ekoslive/media.h b/kstars/ekos/ekoslive/media.h index 5728318c1..bd440bb9c 100644 --- a/kstars/ekos/ekoslive/media.h +++ b/kstars/ekos/ekoslive/media.h @@ -1,110 +1,129 @@ /* Ekos Live Client Copyright (C) 2018 Jasem Mutlaq Media Channel This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include #include #include "ekos/ekos.h" #include "ekos/manager.h" class FITSView; namespace EkosLive { class Media : public QObject { - Q_OBJECT - -public: - Media(Ekos::Manager *manager); - virtual ~Media() = default; - - void sendResponse(const QString &command, const QJsonObject &payload); - void sendResponse(const QString &command, const QJsonArray &payload); - - void setAuthResponse(const QJsonObject &response) {m_AuthResponse = response;} - void setURL(const QUrl &url) {m_URL = url;} - - void registerCameras(); - - // Ekos Media Message to User - void sendPreviewImage(FITSView *view, const QString &uuid); - void sendUpdatedFrame(FITSView *view); - -signals: - void connected(); - void disconnected(); - - void newBoundingRect(QRect rect, QSize view); - -public slots: - void connectServer(); - void disconnectServer(); - - // Capture - void sendVideoFrame(std::unique_ptr & frame); - - // Options - void setOptions(QMap options) {m_Options = options;} - - // Correction Vector - void setCorrectionVector(QLineF correctionVector) { this->correctionVector = correctionVector;} - - // Polar View - void resetPolarView(); - -private slots: - - // Connection - void onConnected(); - void onDisconnected(); - void onError(QAbstractSocket::SocketError error); - - // Communication - void onTextReceived(const QString &message); - void onBinaryReceived(const QByteArray &message); - -private: - QWebSocket m_WebSocket; - QJsonObject m_AuthResponse; - uint16_t m_ReconnectTries {0}; - Ekos::Manager *m_Manager { nullptr }; - QUrl m_URL; - - QMap m_Options; - - QString extension; - QStringList temporaryFiles; - QLineF correctionVector; - - bool m_isConnected { false }; - bool m_sendBlobs { true}; - - // Image width for high-bandwidth setting - static const uint16_t HB_WIDTH = 640; - // Image high bandwidth image quality (jpg) - static const uint8_t HB_IMAGE_QUALITY = 76; - // Video high bandwidth video quality (jpg) - static const uint8_t HB_VIDEO_QUALITY = 64; - // Image high bandwidth image quality (jpg) for PAH - static const uint8_t HB_PAH_IMAGE_QUALITY = 50; - // Video high bandwidth video quality (jpg) for PAH - static const uint8_t HB_PAH_VIDEO_QUALITY = 25; - - // Retry every 5 seconds in case remote server is down - static const uint16_t RECONNECT_INTERVAL = 5000; - // Retry for 1 hour before giving up - static const uint16_t RECONNECT_MAX_TRIES = 720; + Q_OBJECT + + public: + Media(Ekos::Manager * manager); + virtual ~Media() = default; + + void sendResponse(const QString &command, const QJsonObject &payload); + void sendResponse(const QString &command, const QJsonArray &payload); + + void setAuthResponse(const QJsonObject &response) + { + m_AuthResponse = response; + } + void setURL(const QUrl &url) + { + m_URL = url; + } + + void registerCameras(); + + // Ekos Media Message to User + void sendPreviewImage(const QString &filename, const QString &uuid); + void sendPreviewImage(FITSView * view, const QString &uuid); + void sendUpdatedFrame(FITSView * view); + + signals: + void connected(); + void disconnected(); + + void newBoundingRect(QRect rect, QSize view); + + public slots: + void connectServer(); + void disconnectServer(); + + // Capture + void sendVideoFrame(std::unique_ptr &frame); + + // Options + void setOptions(QMap options) + { + m_Options = options; + } + + // Correction Vector + void setCorrectionVector(QLineF correctionVector) + { + this->correctionVector = correctionVector; + } + + // Polar View + void resetPolarView(); + + private slots: + // Connection + void onConnected(); + void onDisconnected(); + void onError(QAbstractSocket::SocketError error); + + // Communication + void onTextReceived(const QString &message); + void onBinaryReceived(const QByteArray &message); + + // Send image + void sendImage(); + + private: + void upload(FITSView * view); + + QWebSocket m_WebSocket; + QJsonObject m_AuthResponse; + uint16_t m_ReconnectTries {0}; + Ekos::Manager * m_Manager { nullptr }; + QUrl m_URL; + QString m_UUID; + + QMap m_Options; + std::unique_ptr previewImage; + + QString extension; + QStringList temporaryFiles; + QLineF correctionVector; + + bool m_isConnected { false }; + bool m_sendBlobs { true}; + + // Image width for high-bandwidth setting + static const uint16_t HB_WIDTH = 640; + // Image high bandwidth image quality (jpg) + static const uint8_t HB_IMAGE_QUALITY = 76; + // Video high bandwidth video quality (jpg) + static const uint8_t HB_VIDEO_QUALITY = 64; + // Image high bandwidth image quality (jpg) for PAH + static const uint8_t HB_PAH_IMAGE_QUALITY = 50; + // Video high bandwidth video quality (jpg) for PAH + static const uint8_t HB_PAH_VIDEO_QUALITY = 25; + + // Retry every 5 seconds in case remote server is down + static const uint16_t RECONNECT_INTERVAL = 5000; + // Retry for 1 hour before giving up + static const uint16_t RECONNECT_MAX_TRIES = 720; }; } diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp index 283eb34ec..eba49af0f 100644 --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -1,2994 +1,3029 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "manager.h" #include "ekosadaptor.h" #include "kstars.h" #include "kstarsdata.h" #include "opsekos.h" #include "Options.h" #include "profileeditor.h" #include "profilewizard.h" #include "skymap.h" #include "auxiliary/darklibrary.h" #include "auxiliary/QProgressIndicator.h" #include "capture/sequencejob.h" #include "fitsviewer/fitstab.h" #include "fitsviewer/fitsview.h" #include "indi/clientmanager.h" #include "indi/driverinfo.h" #include "indi/drivermanager.h" #include "indi/guimanager.h" #include "indi/indielement.h" #include "indi/indilistener.h" #include "indi/indiproperty.h" #include "indi/indiwebmanager.h" #include "ekoslive/ekosliveclient.h" #include "ekoslive/message.h" #include "ekoslive/media.h" #include #include #include #include #include #include #include #define MAX_REMOTE_INDI_TIMEOUT 15000 #define MAX_LOCAL_INDI_TIMEOUT 5000 namespace Ekos { -Manager::Manager(QWidget *parent) : QDialog(parent) +Manager::Manager(QWidget * parent) : QDialog(parent) { #ifdef Q_OS_OSX if (Options::independentWindowEkos()) setWindowFlags(Qt::Window); else { setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(changeAlwaysOnTop(Qt::ApplicationState))); } #endif setupUi(this); qRegisterMetaType("Ekos::CommunicationStatus"); qDBusRegisterMetaType(); new EkosAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos", this); setWindowIcon(QIcon::fromTheme("kstars_ekos")); profileModel.reset(new QStandardItemModel(0, 4)); profileModel->setHorizontalHeaderLabels(QStringList() << "id" << "name" << "host" << "port"); captureProgress->setValue(0); sequenceProgress->setValue(0); sequenceProgress->setDecimals(0); sequenceProgress->setFormat("%v"); imageProgress->setValue(0); imageProgress->setDecimals(1); imageProgress->setFormat("%v"); imageProgress->setBarStyle(QRoundProgressBar::StyleLine); countdownTimer.setInterval(1000); connect(&countdownTimer, &QTimer::timeout, this, &Ekos::Manager::updateCaptureCountDown); toolsWidget->setIconSize(QSize(48, 48)); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); // Enable scheduler Tab toolsWidget->setTabEnabled(1, false); // Start/Stop INDI Server connect(processINDIB, &QPushButton::clicked, this, &Ekos::Manager::processINDI); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); // Connect/Disconnect INDI devices connect(connectB, &QPushButton::clicked, this, &Ekos::Manager::connectDevices); connect(disconnectB, &QPushButton::clicked, this, &Ekos::Manager::disconnectDevices); ekosLiveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); ekosLiveClient.reset(new EkosLive::Client(this)); // INDI Control Panel //connect(controlPanelB, &QPushButton::clicked, GUIManager::Instance(), SLOT(show())); - connect(ekosLiveB, &QPushButton::clicked, [&]() { + connect(ekosLiveB, &QPushButton::clicked, [&]() + { ekosLiveClient.get()->show(); ekosLiveClient.get()->raise(); }); connect(this, &Manager::ekosStatusChanged, ekosLiveClient.get()->message(), &EkosLive::Message::setEkosStatingStatus); - connect(ekosLiveClient.get()->message(), &EkosLive::Message::connected, [&]() {ekosLiveB->setIcon(QIcon(":/icons/cloud-online.svg"));}); - connect(ekosLiveClient.get()->message(), &EkosLive::Message::disconnected, [&]() {ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud"));}); + connect(ekosLiveClient.get()->message(), &EkosLive::Message::connected, [&]() + { + ekosLiveB->setIcon(QIcon(":/icons/cloud-online.svg")); + }); + connect(ekosLiveClient.get()->message(), &EkosLive::Message::disconnected, [&]() + { + ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud")); + }); connect(ekosLiveClient.get()->media(), &EkosLive::Media::newBoundingRect, ekosLiveClient.get()->message(), &EkosLive::Message::setBoundingRect); connect(ekosLiveClient.get()->message(), &EkosLive::Message::resetPolarView, ekosLiveClient.get()->media(), &EkosLive::Media::resetPolarView); // Serial Port Assistat - connect(serialPortAssistantB, &QPushButton::clicked, [&]() { + connect(serialPortAssistantB, &QPushButton::clicked, [&]() + { serialPortAssistant->show(); serialPortAssistant->raise(); }); connect(this, &Ekos::Manager::ekosStatusChanged, [&](Ekos::CommunicationStatus status) { indiControlPanelB->setEnabled(status == Ekos::Success); }); - connect(indiControlPanelB, &QPushButton::clicked, [&]() { + connect(indiControlPanelB, &QPushButton::clicked, [&]() + { KStars::Instance()->actionCollection()->action("show_control_panel")->trigger(); }); - connect(optionsB, &QPushButton::clicked, [&]() { + connect(optionsB, &QPushButton::clicked, [&]() + { KStars::Instance()->actionCollection()->action("configure")->trigger(); }); // Save as above, but it appears in all modules connect(ekosOptionsB, &QPushButton::clicked, this, &Ekos::Manager::showEkosOptions); // Clear Ekos Log connect(clearB, &QPushButton::clicked, this, &Ekos::Manager::clearLog); // Logs - KConfigDialog *dialog = new KConfigDialog(this, "logssettings", Options::self()); + KConfigDialog * dialog = new KConfigDialog(this, "logssettings", Options::self()); opsLogs = new Ekos::OpsLogs(); - KPageWidgetItem *page = dialog->addPage(opsLogs, i18n("Logging")); + KPageWidgetItem * page = dialog->addPage(opsLogs, i18n("Logging")); page->setIcon(QIcon::fromTheme("configure")); connect(logsB, &QPushButton::clicked, dialog, &KConfigDialog::show); connect(dialog->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); connect(dialog->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); // Summary // previewPixmap = new QPixmap(QPixmap(":/images/noimage.png")); // Profiles connect(addProfileB, &QPushButton::clicked, this, &Ekos::Manager::addProfile); connect(editProfileB, &QPushButton::clicked, this, &Ekos::Manager::editProfile); connect(deleteProfileB, &QPushButton::clicked, this, &Ekos::Manager::deleteProfile); connect(profileCombo, static_cast(&QComboBox::currentTextChanged), [=](const QString &text) { Options::setProfile(text); if (text == "Simulators") { editProfileB->setEnabled(false); deleteProfileB->setEnabled(false); } else { editProfileB->setEnabled(true); deleteProfileB->setEnabled(true); } }); // Ekos Wizard connect(wizardProfileB, &QPushButton::clicked, this, &Ekos::Manager::wizardProfile); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Set Profile icons addProfileB->setIcon(QIcon::fromTheme("list-add")); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setIcon(QIcon::fromTheme("document-edit")); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setIcon(QIcon::fromTheme("list-remove")); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); wizardProfileB->setIcon(QIcon::fromTheme("tools-wizard")); wizardProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); customDriversB->setIcon(QIcon::fromTheme("roll")); customDriversB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(customDriversB, &QPushButton::clicked, DriverManager::Instance(), &DriverManager::showCustomDrivers); // Load all drivers loadDrivers(); // Load add driver profiles loadProfiles(); // INDI Control Panel and Ekos Options optionsB->setIcon(QIcon::fromTheme("configure", QIcon(":/icons/ekos_setup.png"))); optionsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Setup Tab toolsWidget->tabBar()->setTabIcon(0, QIcon(":/icons/ekos_setup.png")); toolsWidget->tabBar()->setTabToolTip(0, i18n("Setup")); // Initialize Ekos Scheduler Module schedulerProcess.reset(new Ekos::Scheduler()); toolsWidget->addTab(schedulerProcess.get(), QIcon(":/icons/ekos_scheduler.png"), ""); toolsWidget->tabBar()->setTabToolTip(1, i18n("Scheduler")); connect(schedulerProcess.get(), &Scheduler::newLog, this, &Ekos::Manager::updateLog); //connect(schedulerProcess.get(), SIGNAL(newTarget(QString)), mountTarget, SLOT(setText(QString))); - connect(schedulerProcess.get(), &Ekos::Scheduler::newTarget, [&](const QString &target) { + connect(schedulerProcess.get(), &Ekos::Scheduler::newTarget, [&](const QString &target) + { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); // Temporary fix. Not sure how to resize Ekos Dialog to fit contents of the various tabs in the QScrollArea which are added // dynamically. I used setMinimumSize() but it doesn't appear to make any difference. // Also set Layout policy to SetMinAndMaxSize as well. Any idea how to fix this? // FIXME //resize(1000,750); summaryPreview.reset(new FITSView(previewWidget, FITS_NORMAL)); previewWidget->setContentsMargins(0, 0, 0, 0); summaryPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); summaryPreview->setBaseSize(previewWidget->size()); summaryPreview->createFloatingToolBar(); summaryPreview->setCursorMode(FITSView::dragCursor); - QVBoxLayout *vlayout = new QVBoxLayout(); + QVBoxLayout * vlayout = new QVBoxLayout(); vlayout->addWidget(summaryPreview.get()); previewWidget->setLayout(vlayout); // JM 2019-01-19: Why cloud images depend on summary preview? // connect(summaryPreview.get(), &FITSView::loaded, [&]() // { // // UUID binds the cloud & preview frames by a common key // QString uuid = QUuid::createUuid().toString(); // uuid = uuid.remove(QRegularExpression("[-{}]")); // //ekosLiveClient.get()->media()->sendPreviewImage(summaryPreview.get(), uuid); // ekosLiveClient.get()->cloud()->sendPreviewImage(summaryPreview.get(), uuid); // }); if (Options::ekosLeftIcons()) { toolsWidget->setTabPosition(QTabWidget::West); QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(0); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(0, icon); icon = toolsWidget->tabIcon(1); pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(1, icon); } //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); resize(Options::ekosWindowWidth(), Options::ekosWindowHeight()); } void Manager::changeAlwaysOnTop(Qt::ApplicationState state) { if (isVisible()) { if (state == Qt::ApplicationActive) setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); else setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); } } Manager::~Manager() { toolsWidget->disconnect(this); //delete previewPixmap; } void Manager::closeEvent(QCloseEvent * /*event*/) { - QAction *a = KStars::Instance()->actionCollection()->action("show_ekos"); + QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); Options::setEkosWindowWidth(width()); Options::setEkosWindowHeight(height()); } void Manager::hideEvent(QHideEvent * /*event*/) { - QAction *a = KStars::Instance()->actionCollection()->action("show_ekos"); + QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); } void Manager::showEvent(QShowEvent * /*event*/) { - QAction *a = KStars::Instance()->actionCollection()->action("show_ekos"); + QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(true); // Just show the profile wizard ONCE per session if (profileWizardLaunched == false && profiles.count() == 1) { profileWizardLaunched = true; wizardProfile(); } } void Manager::resizeEvent(QResizeEvent *) { //previewImage->setPixmap(previewPixmap->scaled(previewImage->width(), previewImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (focusStarPixmap.get() != nullptr) focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), - Qt::KeepAspectRatio, Qt::SmoothTransformation)); + Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (focusProfilePixmap) //focusProfileImage->setPixmap(focusProfilePixmap->scaled(focusProfileImage->width(), focusProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (guideStarPixmap.get() != nullptr) guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), - Qt::KeepAspectRatio, Qt::SmoothTransformation)); + Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (guideProfilePixmap) //guideProfileImage->setPixmap(guideProfilePixmap->scaled(guideProfileImage->width(), guideProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::loadProfiles() { profiles.clear(); KStarsData::Instance()->userdb()->GetAllProfiles(profiles); profileModel->clear(); - for (auto& pi : profiles) + for (auto &pi : profiles) { QList info; info << new QStandardItem(pi->id) << new QStandardItem(pi->name) << new QStandardItem(pi->host) << new QStandardItem(pi->port); profileModel->appendRow(info); } profileModel->sort(0); profileCombo->blockSignals(true); profileCombo->setModel(profileModel.get()); profileCombo->setModelColumn(1); profileCombo->blockSignals(false); // Load last used profile from options int index = profileCombo->findText(Options::profile()); // If not found, set it to first item if (index == -1) index = 0; profileCombo->setCurrentIndex(index); } void Manager::loadDrivers() { - foreach (DriverInfo *dv, DriverManager::Instance()->getDrivers()) + foreach (DriverInfo * dv, DriverManager::Instance()->getDrivers()) { if (dv->getDriverSource() != HOST_SOURCE) driversList[dv->getLabel()] = dv; } } void Manager::reset() { qCDebug(KSTARS_EKOS) << "Resetting Ekos Manager..."; // Filter Manager filterManager.reset(new Ekos::FilterManager()); nDevices = 0; useGuideHead = false; useST4 = false; removeTabs(); genericDevices.clear(); managedDevices.clear(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); domeProcess.reset(); alignProcess.reset(); mountProcess.reset(); weatherProcess.reset(); dustCapProcess.reset(); Ekos::CommunicationStatus previousStatus = m_ekosStatus; m_ekosStatus = Ekos::Idle; if (previousStatus != m_ekosStatus) emit ekosStatusChanged(m_ekosStatus); previousStatus = m_indiStatus; m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); processINDIB->setEnabled(true); mountGroup->setEnabled(false); focusGroup->setEnabled(false); captureGroup->setEnabled(false); guideGroup->setEnabled(false); sequenceLabel->setText(i18n("Sequence")); sequenceProgress->setValue(0); captureProgress->setValue(0); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); imageRemainingTime->setText("--:--:--"); mountStatus->setText(i18n("Idle")); captureStatus->setText(i18n("Idle")); focusStatus->setText(i18n("Idle")); guideStatus->setText(i18n("Idle")); if (capturePI) capturePI->stopAnimation(); if (mountPI) mountPI->stopAnimation(); if (focusPI) focusPI->stopAnimation(); if (guidePI) guidePI->stopAnimation(); m_isStarted = false; //processINDIB->setText(i18n("Start INDI")); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); } void Manager::processINDI() { if (m_isStarted == false) start(); else stop(); } bool Manager::stop() { cleanDevices(); serialPortAssistant.reset(); serialPortAssistantB->setEnabled(false); profileGroup->setEnabled(true); return true; } bool Manager::start() { // Don't start if it is already started before if (m_ekosStatus == Ekos::Pending || m_ekosStatus == Ekos::Success) { qCDebug(KSTARS_EKOS) << "Ekos Manager start called but current Ekos Status is" << m_ekosStatus << "Ignoring request."; return true; } if (m_LocalMode) qDeleteAll(managedDrivers); managedDrivers.clear(); // If clock was paused, unpaused it and sync time if (KStarsData::Instance()->clock()->isActive() == false) { KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); KStarsData::Instance()->clock()->start(); } reset(); currentProfile = getCurrentProfile(); m_LocalMode = currentProfile->isLocal(); // Load profile location if one exists updateProfileLocation(currentProfile); bool haveCCD = false, haveGuider = false; if (currentProfile->guidertype == Ekos::Guide::GUIDE_PHD2) { Options::setPHD2Host(currentProfile->guiderhost); Options::setPHD2Port(currentProfile->guiderport); } else if (currentProfile->guidertype == Ekos::Guide::GUIDE_LINGUIDER) { Options::setLinGuiderHost(currentProfile->guiderhost); Options::setLinGuiderPort(currentProfile->guiderport); } if (m_LocalMode) { - DriverInfo *drv = driversList.value(currentProfile->mount()); + DriverInfo * drv = driversList.value(currentProfile->mount()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->ccd()); if (drv != nullptr) { managedDrivers.append(drv->clone()); haveCCD = true; } Options::setGuiderType(currentProfile->guidertype); drv = driversList.value(currentProfile->guider()); if (drv != nullptr) { haveGuider = true; // If the guider and ccd are the same driver, we have two cases: // #1 Drivers that only support ONE device per driver (such as sbig) // #2 Drivers that supports multiples devices per driver (such as sx) // For #1, we modify guider_di to make a unique label for the other device with postfix "Guide" // For #2, we set guider_di to nullptr and we prompt the user to select which device is primary ccd and which is guider // since this is the only way to find out in real time. if (haveCCD && currentProfile->guider() == currentProfile->ccd()) { if (drv->getAuxInfo().value("mdpd", false).toBool() == true) { drv = nullptr; } else { drv->setUniqueLabel(drv->getLabel() + " Guide"); } } if (drv) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->ao()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->filter()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->focuser()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->dome()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->weather()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux1()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux2()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux3()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux4()); if (drv != nullptr) managedDrivers.append(drv->clone()); // Add remote drivers if we have any if (currentProfile->remotedrivers.isEmpty() == false && currentProfile->remotedrivers.contains("@")) { for (auto remoteDriver : currentProfile->remotedrivers.split(",")) { QString name, label, host("localhost"), port("7624"); QStringList properties = remoteDriver.split(QRegExp("[@:]")); if (properties.length() > 1) { name = properties[0]; host = properties[1]; if (properties.length() > 2) port = properties[2]; } - DriverInfo *dv = new DriverInfo(name); + DriverInfo * dv = new DriverInfo(name); dv->setRemoteHost(host); dv->setRemotePort(port); label = name; // Remove extra quotes label.remove("\""); dv->setLabel(label); dv->setUniqueLabel(label); managedDrivers.append(dv); } } if (haveCCD == false && haveGuider == false) { KMessageBox::error(this, i18n("Ekos requires at least one CCD or Guider to operate.")); managedDrivers.clear(); return false; } nDevices = managedDrivers.count(); } else { - DriverInfo *remote_indi = new DriverInfo(QString("Ekos Remote Host")); + DriverInfo * remote_indi = new DriverInfo(QString("Ekos Remote Host")); remote_indi->setHostParameters(currentProfile->host, QString::number(currentProfile->port)); remote_indi->setDriverSource(GENERATED_SOURCE); managedDrivers.append(remote_indi); haveCCD = currentProfile->drivers.contains("CCD"); haveGuider = currentProfile->drivers.contains("Guider"); Options::setGuiderType(currentProfile->guidertype); if (haveCCD == false && haveGuider == false) { KMessageBox::error(this, i18n("Ekos requires at least one CCD or Guider to operate.")); delete (remote_indi); nDevices = 0; return false; } nDevices = currentProfile->drivers.count(); } connect(INDIListener::Instance(), &INDIListener::newDevice, this, &Ekos::Manager::processNewDevice); connect(INDIListener::Instance(), &INDIListener::newTelescope, this, &Ekos::Manager::setTelescope); connect(INDIListener::Instance(), &INDIListener::newCCD, this, &Ekos::Manager::setCCD); connect(INDIListener::Instance(), &INDIListener::newFilter, this, &Ekos::Manager::setFilter); connect(INDIListener::Instance(), &INDIListener::newFocuser, this, &Ekos::Manager::setFocuser); connect(INDIListener::Instance(), &INDIListener::newDome, this, &Ekos::Manager::setDome); connect(INDIListener::Instance(), &INDIListener::newWeather, this, &Ekos::Manager::setWeather); connect(INDIListener::Instance(), &INDIListener::newDustCap, this, &Ekos::Manager::setDustCap); connect(INDIListener::Instance(), &INDIListener::newLightBox, this, &Ekos::Manager::setLightBox); connect(INDIListener::Instance(), &INDIListener::newST4, this, &Ekos::Manager::setST4); connect(INDIListener::Instance(), &INDIListener::deviceRemoved, this, &Ekos::Manager::removeDevice, Qt::DirectConnection); #ifdef Q_OS_OSX if (m_LocalMode||currentProfile->host=="localhost") { if (isRunning("PTPCamera")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(0, i18n("Ekos detected that PTP Camera is running and may prevent a Canon or Nikon camera from connecting to Ekos. Do you want to quit PTP Camera now?"), i18n("PTP Camera"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_PTPCamera"))) { //TODO is there a better way to do this. QProcess p; p.start("killall PTPCamera"); p.waitForFinished(); } } } #endif if (m_LocalMode) { if (isRunning("indiserver")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(nullptr, i18n("Ekos detected an instance of INDI server running. Do you wish to " - "shut down the existing instance before starting a new one?"), + "shut down the existing instance before starting a new one?"), i18n("INDI Server"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_existing_indiserver"))) { DriverManager::Instance()->stopAllDevices(); //TODO is there a better way to do this. QProcess p; p.start("pkill indiserver"); p.waitForFinished(); } } appendLogText(i18n("Starting INDI services...")); if (DriverManager::Instance()->startDevices(managedDrivers) == false) { INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString,QString)), this, SLOT(processServerTermination(QString,QString))); m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); if (currentProfile->autoConnect) appendLogText(i18n("INDI services started on port %1.", managedDrivers.first()->getPort())); else appendLogText( - i18n("INDI services started on port %1. Please connect devices.", managedDrivers.first()->getPort())); + i18n("INDI services started on port %1. Please connect devices.", managedDrivers.first()->getPort())); QTimer::singleShot(MAX_LOCAL_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); } else { // If we need to use INDI Web Manager if (currentProfile->INDIWebManagerPort > 0) { appendLogText(i18n("Establishing communication with remote INDI Web Manager...")); m_RemoteManagerStart = false; if (INDI::WebManager::isOnline(currentProfile)) { INDI::WebManager::syncCustomDrivers(currentProfile); currentProfile->isStellarMate = INDI::WebManager::isStellarMate(currentProfile); if (INDI::WebManager::areDriversRunning(currentProfile) == false) { INDI::WebManager::stopProfile(currentProfile); if (INDI::WebManager::startProfile(currentProfile) == false) { appendLogText(i18n("Failed to start profile on remote INDI Web Manager.")); return false; } appendLogText(i18n("Starting profile on remote INDI Web Manager...")); m_RemoteManagerStart = true; } } else appendLogText(i18n("Warning: INDI Web Manager is not online.")); } appendLogText( - i18n("Connecting to remote INDI server at %1 on port %2 ...", currentProfile->host, currentProfile->port)); + i18n("Connecting to remote INDI server at %1 on port %2 ...", currentProfile->host, currentProfile->port)); qApp->processEvents(); QApplication::setOverrideCursor(Qt::WaitCursor); if (DriverManager::Instance()->connectRemoteHost(managedDrivers.first()) == false) { appendLogText(i18n("Failed to connect to remote INDI server.")); INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); QApplication::restoreOverrideCursor(); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString,QString)), this, SLOT(processServerTermination(QString,QString))); QApplication::restoreOverrideCursor(); m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); appendLogText( - i18n("INDI services started. Connection to remote INDI server is successful. Waiting for devices...")); + i18n("INDI services started. Connection to remote INDI server is successful. Waiting for devices...")); QTimer::singleShot(MAX_REMOTE_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); } connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); profileGroup->setEnabled(false); m_isStarted = true; //processINDIB->setText(i18n("Stop INDI")); processINDIB->setIcon(QIcon::fromTheme("media-playback-stop")); processINDIB->setToolTip(i18n("Stop")); return true; } void Manager::checkINDITimeout() { // Don't check anything unless we're still pending if (m_ekosStatus != Ekos::Pending) return; if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); return; } if (m_LocalMode) { QStringList remainingDevices; - foreach (DriverInfo *drv, managedDrivers) + foreach (DriverInfo * drv, managedDrivers) { if (drv->getDevices().count() == 0) remainingDevices << QString("+ %1").arg( - drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName()); + drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName()); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish:\n%1\nPlease ensure the device is connected and powered on.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish the following devices:\n%1\nPlease ensure each device is connected " "and powered on.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } else { QStringList remainingDevices; for (auto &driver : currentProfile->drivers.values()) { bool driverFound = false; for (auto &device : genericDevices) { if (device->getBaseDevice()->getDriverName() == driver) { driverFound = true; break; } } if (driverFound == false) remainingDevices << QString("+ %1").arg(driver); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish remote device:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish remote devices:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } m_ekosStatus = Ekos::Error; } void Manager::connectDevices() { // Check if already connected int nConnected = 0; Ekos::CommunicationStatus previousStatus = m_indiStatus; for (auto &device : genericDevices) { if (device->isConnected()) nConnected++; } if (genericDevices.count() == nConnected) { m_indiStatus = Ekos::Success; emit indiStatusChanged(m_indiStatus); return; } m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName(); device->Connect(); } connectB->setEnabled(false); disconnectB->setEnabled(true); appendLogText(i18n("Connecting INDI devices...")); } void Manager::disconnectDevices() { for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName(); device->Disconnect(); } appendLogText(i18n("Disconnecting INDI devices...")); } void Manager::processServerTermination(const QString &host, const QString &port) { if ((m_LocalMode && managedDrivers.first()->getPort() == port) || (currentProfile->host == host && currentProfile->port == port.toInt())) { cleanDevices(false); } } void Manager::cleanDevices(bool stopDrivers) { if (m_ekosStatus == Ekos::Idle) return; INDIListener::Instance()->disconnect(this); DriverManager::Instance()->disconnect(this); if (managedDrivers.isEmpty() == false) { if (m_LocalMode) { if (stopDrivers) DriverManager::Instance()->stopDevices(managedDrivers); } else { if (stopDrivers) DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first()); if (m_RemoteManagerStart && currentProfile->INDIWebManagerPort != -1) { INDI::WebManager::stopProfile(currentProfile); m_RemoteManagerStart = false; } } } reset(); profileGroup->setEnabled(true); appendLogText(i18n("INDI services stopped.")); } -void Manager::processNewDevice(ISD::GDInterface *devInterface) +void Manager::processNewDevice(ISD::GDInterface * devInterface) { qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << devInterface->getDeviceName(); Ekos::CommunicationStatus previousStatus = m_indiStatus; for(auto &device: genericDevices) { if (!strcmp(device->getDeviceName(), devInterface->getDeviceName())) { qCWarning(KSTARS_EKOS) << "Found duplicate device, ignoring..."; return; } } // Always reset INDI Connection status if we receive a new device m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); genericDevices.append(devInterface); nDevices--; connect(devInterface, &ISD::GDInterface::Connected, this, &Ekos::Manager::deviceConnected); connect(devInterface, &ISD::GDInterface::Disconnected, this, &Ekos::Manager::deviceDisconnected); connect(devInterface, &ISD::GDInterface::propertyDefined, this, &Ekos::Manager::processNewProperty); if (currentProfile->isStellarMate) { - connect(devInterface, &ISD::GDInterface::systemPortDetected, [this,devInterface]() { + connect(devInterface, &ISD::GDInterface::systemPortDetected, [this,devInterface]() + { if (!serialPortAssistant) { serialPortAssistant.reset(new SerialPortAssistant(currentProfile, this)); serialPortAssistantB->setEnabled(true); } uint32_t driverInterface = devInterface->getDriverInterface(); // Ignore CCD interface if (driverInterface & INDI::BaseDevice::CCD_INTERFACE) return; if (driverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE || - driverInterface & INDI::BaseDevice::FOCUSER_INTERFACE || - driverInterface & INDI::BaseDevice::FILTER_INTERFACE || - driverInterface & INDI::BaseDevice::AUX_INTERFACE || - driverInterface & INDI::BaseDevice::GPS_INTERFACE) - serialPortAssistant->addDevice(devInterface); + driverInterface & INDI::BaseDevice::FOCUSER_INTERFACE || + driverInterface & INDI::BaseDevice::FILTER_INTERFACE || + driverInterface & INDI::BaseDevice::AUX_INTERFACE || + driverInterface & INDI::BaseDevice::GPS_INTERFACE) + serialPortAssistant->addDevice(devInterface); if (Options::autoLoadSerialAssistant()) - serialPortAssistant->show(); + serialPortAssistant->show(); }); } if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); //controlPanelB->setEnabled(true); if (m_LocalMode == false && nDevices == 0) { if (currentProfile->autoConnect) appendLogText(i18n("Remote devices established.")); else appendLogText(i18n("Remote devices established. Please connect devices.")); } } } void Manager::deviceConnected() { connectB->setEnabled(false); disconnectB->setEnabled(true); processINDIB->setEnabled(false); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (Options::verboseLogging()) { - ISD::GDInterface *device = qobject_cast(sender()); + ISD::GDInterface * device = qobject_cast(sender()); qCInfo(KSTARS_EKOS) << device->getDeviceName() << "is connected."; } int nConnectedDevices = 0; - foreach (ISD::GDInterface *device, genericDevices) + foreach (ISD::GDInterface * device, genericDevices) { if (device->isConnected()) nConnectedDevices++; } qCDebug(KSTARS_EKOS) << nConnectedDevices << " devices connected out of " << genericDevices.count(); //if (nConnectedDevices >= pi->drivers.count()) if (nConnectedDevices >= genericDevices.count()) { m_indiStatus = Ekos::Success; qCInfo(KSTARS_EKOS)<< "All INDI devices are now connected."; } else m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); - ISD::GDInterface *dev = static_cast(sender()); + ISD::GDInterface * dev = static_cast(sender()); if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE) { if (mountProcess.get() != nullptr) { mountProcess->setEnabled(true); if (alignProcess.get() != nullptr) alignProcess->setEnabled(true); } } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(true); if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); if (alignProcess.get() != nullptr) { if (mountProcess.get() && mountProcess->isEnabled()) alignProcess->setEnabled(true); else alignProcess->setEnabled(false); } if (guideProcess.get() != nullptr) guideProcess->setEnabled(true); } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); } if (Options::neverLoadConfig()) return; INDIConfig tConfig = Options::loadConfigOnConnection() ? LOAD_LAST_CONFIG : LOAD_DEFAULT_CONFIG; - foreach (ISD::GDInterface *device, genericDevices) + foreach (ISD::GDInterface * device, genericDevices) { if (device == dev) { connect(dev, &ISD::GDInterface::switchUpdated, this, &Ekos::Manager::watchDebugProperty); - ISwitchVectorProperty *configProp = device->getBaseDevice()->getSwitch("CONFIG_PROCESS"); + ISwitchVectorProperty * configProp = device->getBaseDevice()->getSwitch("CONFIG_PROCESS"); if (configProp && configProp->s == IPS_IDLE) device->setConfig(tConfig); break; } } } void Manager::deviceDisconnected() { - ISD::GDInterface *dev = static_cast(sender()); + ISD::GDInterface * dev = static_cast(sender()); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (dev != nullptr) { if (dev->getState("CONNECTION") == IPS_ALERT) m_indiStatus = Ekos::Error; else if (dev->getState("CONNECTION") == IPS_BUSY) m_indiStatus = Ekos::Pending; else m_indiStatus = Ekos::Idle; if (Options::verboseLogging()) qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected."; appendLogText(i18n("%1 is disconnected.", dev->getDeviceName())); } else m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); processINDIB->setEnabled(true); if (dev != nullptr && dev->getBaseDevice() && (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) { if (mountProcess.get() != nullptr) mountProcess->setEnabled(false); } // Do not disable modules on device connection loss, let them handle it /* else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(false); if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); if (alignProcess.get() != nullptr) alignProcess->setEnabled(false); if (guideProcess.get() != nullptr) guideProcess->setEnabled(false); } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); }*/ } -void Manager::setTelescope(ISD::GDInterface *scopeDevice) +void Manager::setTelescope(ISD::GDInterface * scopeDevice) { //mount = scopeDevice; managedDevices[KSTARS_TELESCOPE] = scopeDevice; appendLogText(i18n("%1 is online.", scopeDevice->getDeviceName())); - connect(scopeDevice, SIGNAL(numberUpdated(INumberVectorProperty*)), this, - SLOT(processNewNumber(INumberVectorProperty*)), Qt::UniqueConnection); + connect(scopeDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, + SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); initMount(); mountProcess->setTelescope(scopeDevice); double primaryScopeFL=0, primaryScopeAperture=0, guideScopeFL=0, guideScopeAperture=0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver mountProcess->setTelescopeInfo(QList() << primaryScopeFL << primaryScopeAperture << guideScopeFL << guideScopeAperture); if (guideProcess.get() != nullptr) { guideProcess->setTelescope(scopeDevice); guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(scopeDevice); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } if (domeProcess.get() != nullptr) domeProcess->setTelescope(scopeDevice); ekosLiveClient->message()->sendMounts(); ekosLiveClient->message()->sendScopes(); } -void Manager::setCCD(ISD::GDInterface *ccdDevice) +void Manager::setCCD(ISD::GDInterface * ccdDevice) { // No duplicates for (auto oneCCD : findDevices(KSTARS_CCD)) if (oneCCD == ccdDevice) return; managedDevices.insertMulti(KSTARS_CCD, ccdDevice); initCapture(); captureProcess->setEnabled(true); captureProcess->addCCD(ccdDevice); QString primaryCCD, guiderCCD; // Only look for primary & guider CCDs if we can tell a difference between them // otherwise rely on saved options if (currentProfile->ccd() != currentProfile->guider()) { - foreach (ISD::GDInterface *device, findDevices(KSTARS_CCD)) + foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (QString(device->getDeviceName()).startsWith(currentProfile->ccd(), Qt::CaseInsensitive)) primaryCCD = QString(device->getDeviceName()); else if (QString(device->getDeviceName()).startsWith(currentProfile->guider(), Qt::CaseInsensitive)) guiderCCD = QString(device->getDeviceName()); } } bool rc = false; if (Options::defaultCaptureCCD().isEmpty() == false) rc = captureProcess->setCamera(Options::defaultCaptureCCD()); if (rc == false && primaryCCD.isEmpty() == false) captureProcess->setCamera(primaryCCD); initFocus(); focusProcess->addCCD(ccdDevice); rc = false; if (Options::defaultFocusCCD().isEmpty() == false) rc = focusProcess->setCamera(Options::defaultFocusCCD()); if (rc == false && primaryCCD.isEmpty() == false) focusProcess->setCamera(primaryCCD); initAlign(); alignProcess->addCCD(ccdDevice); rc = false; if (Options::defaultAlignCCD().isEmpty() == false) rc = alignProcess->setCamera(Options::defaultAlignCCD()); if (rc == false && primaryCCD.isEmpty() == false) alignProcess->setCamera(primaryCCD); initGuide(); guideProcess->addCamera(ccdDevice); rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false && guiderCCD.isEmpty() == false) guideProcess->setCamera(guiderCCD); appendLogText(i18n("%1 is online.", ccdDevice->getDeviceName())); - connect(ccdDevice, SIGNAL(numberUpdated(INumberVectorProperty*)), this, - SLOT(processNewNumber(INumberVectorProperty*)), Qt::UniqueConnection); + connect(ccdDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, + SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); if (managedDevices.contains(KSTARS_TELESCOPE)) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); } } -void Manager::setFilter(ISD::GDInterface *filterDevice) +void Manager::setFilter(ISD::GDInterface * filterDevice) { // No duplicates if (findDevices(KSTARS_FILTER).contains(filterDevice) == false) managedDevices.insertMulti(KSTARS_FILTER, filterDevice); appendLogText(i18n("%1 filter is online.", filterDevice->getDeviceName())); initCapture(); - connect(filterDevice, SIGNAL(numberUpdated(INumberVectorProperty*)), this, - SLOT(processNewNumber(INumberVectorProperty*)), Qt::UniqueConnection); - connect(filterDevice, SIGNAL(textUpdated(ITextVectorProperty*)), this, - SLOT(processNewText(ITextVectorProperty*)), Qt::UniqueConnection); + connect(filterDevice, SIGNAL(numberUpdated(INumberVectorProperty *)), this, + SLOT(processNewNumber(INumberVectorProperty *)), Qt::UniqueConnection); + connect(filterDevice, SIGNAL(textUpdated(ITextVectorProperty *)), this, + SLOT(processNewText(ITextVectorProperty *)), Qt::UniqueConnection); captureProcess->addFilter(filterDevice); initFocus(); focusProcess->addFilter(filterDevice); initAlign(); alignProcess->addFilter(filterDevice); if (Options::defaultAlignFW().isEmpty() == false) alignProcess->setFilterWheel(Options::defaultAlignFW()); } -void Manager::setFocuser(ISD::GDInterface *focuserDevice) +void Manager::setFocuser(ISD::GDInterface * focuserDevice) { // No duplicates if (findDevices(KSTARS_FOCUSER).contains(focuserDevice) == false) managedDevices.insertMulti(KSTARS_FOCUSER, focuserDevice); initCapture(); initFocus(); focusProcess->addFocuser(focuserDevice); if (Options::defaultFocusFocuser().isEmpty() == false) focusProcess->setFocuser(Options::defaultFocusFocuser()); appendLogText(i18n("%1 focuser is online.", focuserDevice->getDeviceName())); } -void Manager::setDome(ISD::GDInterface *domeDevice) +void Manager::setDome(ISD::GDInterface * domeDevice) { managedDevices[KSTARS_DOME] = domeDevice; initDome(); domeProcess->setDome(domeDevice); if (captureProcess.get() != nullptr) captureProcess->setDome(domeDevice); if (alignProcess.get() != nullptr) alignProcess->setDome(domeDevice); if (managedDevices.contains(KSTARS_TELESCOPE)) domeProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); appendLogText(i18n("%1 is online.", domeDevice->getDeviceName())); } -void Manager::setWeather(ISD::GDInterface *weatherDevice) +void Manager::setWeather(ISD::GDInterface * weatherDevice) { managedDevices[KSTARS_WEATHER] = weatherDevice; initWeather(); weatherProcess->setWeather(weatherDevice); appendLogText(i18n("%1 is online.", weatherDevice->getDeviceName())); } -void Manager::setDustCap(ISD::GDInterface *dustCapDevice) +void Manager::setDustCap(ISD::GDInterface * dustCapDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(dustCapDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, dustCapDevice); initDustCap(); dustCapProcess->setDustCap(dustCapDevice); appendLogText(i18n("%1 is online.", dustCapDevice->getDeviceName())); if (captureProcess.get() != nullptr) captureProcess->setDustCap(dustCapDevice); } -void Manager::setLightBox(ISD::GDInterface *lightBoxDevice) +void Manager::setLightBox(ISD::GDInterface * lightBoxDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(lightBoxDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, lightBoxDevice); if (captureProcess.get() != nullptr) captureProcess->setLightBox(lightBoxDevice); } -void Manager::removeDevice(ISD::GDInterface *devInterface) +void Manager::removeDevice(ISD::GDInterface * devInterface) { switch (devInterface->getType()) { - case KSTARS_CCD: - removeTabs(); - break; + case KSTARS_CCD: + removeTabs(); + break; - case KSTARS_TELESCOPE: - if (mountProcess.get() != nullptr) - { - mountProcess.reset(); - } - break; + case KSTARS_TELESCOPE: + if (mountProcess.get() != nullptr) + { + mountProcess.reset(); + } + break; - case KSTARS_FOCUSER: - // TODO this should be done for all modules - if (focusProcess.get() != nullptr) - focusProcess.get()->removeDevice(devInterface); - break; + case KSTARS_FOCUSER: + // TODO this should be done for all modules + if (focusProcess.get() != nullptr) + focusProcess.get()->removeDevice(devInterface); + break; - default: - break; + default: + break; } appendLogText(i18n("%1 is offline.", devInterface->getDeviceName())); // #1 Remove from Generic Devices // Generic devices are ALL the devices we receive from INDI server // Whether Ekos cares about them (i.e. selected equipment) or extra devices we // do not care about - foreach (ISD::GDInterface *genericDevice, genericDevices) + foreach (ISD::GDInterface * genericDevice, genericDevices) if (!strcmp(genericDevice->getDeviceName(), devInterface->getDeviceName())) { genericDevices.removeOne(genericDevice); break; } // #2 Remove from Ekos Managed Device // Managed devices are devices selected by the user in the device profile - foreach (ISD::GDInterface *device, managedDevices.values()) + foreach (ISD::GDInterface * device, managedDevices.values()) { if (device == devInterface) { managedDevices.remove(managedDevices.key(device)); if (managedDevices.count() == 0) cleanDevices(); break; } } } -void Manager::processNewText(ITextVectorProperty *tvp) +void Manager::processNewText(ITextVectorProperty * tvp) { if (!strcmp(tvp->name, "FILTER_NAME")) { ekosLiveClient.get()->message()->sendFilterWheels(); } } -void Manager::processNewNumber(INumberVectorProperty *nvp) +void Manager::processNewNumber(INumberVectorProperty * nvp) { if (!strcmp(nvp->name, "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(nvp->name, "CCD_INFO") || !strcmp(nvp->name, "GUIDER_INFO") || !strcmp(nvp->name, "CCD_FRAME") || !strcmp(nvp->name, "GUIDER_FRAME")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } /* if (!strcmp(nvp->name, "FILTER_SLOT")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); } */ } -void Manager::processNewProperty(INDI::Property *prop) +void Manager::processNewProperty(INDI::Property * prop) { - ISD::GenericDevice *deviceInterface = qobject_cast(sender()); + ISD::GenericDevice * deviceInterface = qobject_cast(sender()); if (!strcmp(prop->getName(), "CONNECTION") && currentProfile->autoConnect) { deviceInterface->Connect(); return; } // Check if we need to turn on DEBUG for logging purposes if (!strcmp(prop->getName(), "DEBUG")) { uint16_t interface = deviceInterface->getBaseDevice()->getDriverInterface(); if ( opsLogs->getINDIDebugInterface() & interface ) { // Check if we need to enable debug logging for the INDI drivers. - ISwitchVectorProperty *debugSP = prop->getSwitch(); + ISwitchVectorProperty * debugSP = prop->getSwitch(); debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); } } // Handle debug levels for logging purposes if (!strcmp(prop->getName(), "DEBUG_LEVEL")) { uint16_t interface = deviceInterface->getBaseDevice()->getDriverInterface(); // Check if the logging option for the specific device class is on and if the device interface matches it. if ( opsLogs->getINDIDebugInterface() & interface ) { // Turn on everything - ISwitchVectorProperty *debugLevel = prop->getSwitch(); + ISwitchVectorProperty * debugLevel = prop->getSwitch(); for (int i=0; i < debugLevel->nsp; i++) debugLevel->sp[i].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugLevel); } } if (!strcmp(prop->getName(), "TELESCOPE_INFO") || !strcmp(prop->getName(), "TELESCOPE_SLEW_RATE") || !strcmp(prop->getName(), "TELESCOPE_PARK")) { ekosLiveClient.get()->message()->sendMounts(); ekosLiveClient.get()->message()->sendScopes(); } if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "CCD_TEMPERATURE") || !strcmp(prop->getName(), "CCD_ISO") || - !strcmp(prop->getName(), "CCD_GAIN") || !strcmp(prop->getName(), "CCD_CONTROLS")) + !strcmp(prop->getName(), "CCD_GAIN") || !strcmp(prop->getName(), "CCD_CONTROLS")) { ekosLiveClient.get()->message()->sendCameras(); ekosLiveClient.get()->media()->registerCameras(); } if (!strcmp(prop->getName(), "ABS_DOME_POSITION") || !strcmp(prop->getName(), "DOME_ABORT_MOTION") || - !strcmp(prop->getName(), "DOME_PARK")) + !strcmp(prop->getName(), "DOME_PARK")) { ekosLiveClient.get()->message()->sendDomes(); } if (!strcmp(prop->getName(), "CAP_PARK") || !strcmp(prop->getName(), "FLAT_LIGHT_CONTROL")) { ekosLiveClient.get()->message()->sendCaps(); } if (!strcmp(prop->getName(), "FILTER_NAME")) ekosLiveClient.get()->message()->sendFilterWheels(); if (!strcmp(prop->getName(), "FILTER_NAME")) filterManager.data()->initFilterProperties(); if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "GUIDER_INFO")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } if (!strcmp(prop->getName(), "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(prop->getName(), "GUIDER_EXPOSURE")) { - foreach (ISD::GDInterface *device, findDevices(KSTARS_CCD)) + foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { initCapture(); initGuide(); useGuideHead = true; captureProcess->addGuideHead(device); guideProcess->addGuideHead(device); bool rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false) guideProcess->setCamera(QString(device->getDeviceName()) + QString(" Guider")); return; } } return; } if (!strcmp(prop->getName(), "CCD_FRAME_TYPE")) { if (captureProcess.get() != nullptr) { - foreach (ISD::GDInterface *device, findDevices(KSTARS_CCD)) + foreach (ISD::GDInterface * device, findDevices(KSTARS_CCD)) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { captureProcess->syncFrameType(device); return; } } } return; } if (!strcmp(prop->getName(), "TELESCOPE_PARK") && managedDevices.contains(KSTARS_TELESCOPE)) { if (captureProcess.get() != nullptr) captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); if (mountProcess.get() != nullptr) mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); return; } /* if (!strcmp(prop->getName(), "FILTER_NAME")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); return; } */ if (!strcmp(prop->getName(), "ASTROMETRY_SOLVER")) { - foreach (ISD::GDInterface *device, genericDevices) + foreach (ISD::GDInterface * device, genericDevices) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { initAlign(); alignProcess->setAstrometryDevice(device); break; } } } if (!strcmp(prop->getName(), "ABS_ROTATOR_ANGLE")) { managedDevices[KSTARS_ROTATOR] = deviceInterface; if (captureProcess.get() != nullptr) captureProcess->setRotator(deviceInterface); if (alignProcess.get() != nullptr) alignProcess->setRotator(deviceInterface); } if (!strcmp(prop->getName(), "GPS_REFRESH")) { managedDevices.insertMulti(KSTARS_AUXILIARY, deviceInterface); if (mountProcess.get() != nullptr) mountProcess->setGPS(deviceInterface); } if (focusProcess.get() != nullptr && strstr(prop->getName(), "FOCUS_")) { focusProcess->checkFocuser(); } } QList Manager::findDevices(DeviceFamily type) { QList deviceList; QMapIterator i(managedDevices); while (i.hasNext()) { i.next(); if (i.key() == type) deviceList.append(i.value()); } return deviceList; } void Manager::processTabChange() { - QWidget *currentWidget = toolsWidget->currentWidget(); + QWidget * currentWidget = toolsWidget->currentWidget(); //if (focusProcess.get() != nullptr && currentWidget != focusProcess) //focusProcess->resetFrame(); if (alignProcess.get() && alignProcess.get() == currentWidget) { if (alignProcess->isEnabled() == false && captureProcess->isEnabled()) { if (managedDevices[KSTARS_CCD]->isConnected() && managedDevices.contains(KSTARS_TELESCOPE)) { if (alignProcess->isParserOK()) alignProcess->setEnabled(true); //#ifdef Q_OS_WIN else { // If current setting is remote astrometry and profile doesn't contain // remote astrometry, then we switch to online solver. Otherwise, the whole align // module remains disabled and the user cannot change re-enable it since he cannot select online solver - ProfileInfo *pi = getCurrentProfile(); + ProfileInfo * pi = getCurrentProfile(); if (Options::solverType() == Ekos::Align::SOLVER_REMOTE && pi->aux1() != "Astrometry" && pi->aux2() != "Astrometry" && pi->aux3() != "Astrometry" && pi->aux4() != "Astrometry") { Options::setSolverType(Ekos::Align::SOLVER_ONLINE); alignModule()->setSolverType(Ekos::Align::SOLVER_ONLINE); alignProcess->setEnabled(true); } } //#endif } } alignProcess->checkCCD(); } else if (captureProcess.get() != nullptr && currentWidget == captureProcess.get()) { captureProcess->checkCCD(); } else if (focusProcess.get() != nullptr && currentWidget == focusProcess.get()) { focusProcess->checkCCD(); } else if (guideProcess.get() != nullptr && currentWidget == guideProcess.get()) { guideProcess->checkCCD(); } updateLog(); } void Manager::updateLog() { //if (enableLoggingCheck->isChecked() == false) //return; - QWidget *currentWidget = toolsWidget->currentWidget(); + QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) ekosLogOut->setPlainText(m_LogText.join("\n")); else if (currentWidget == alignProcess.get()) ekosLogOut->setPlainText(alignProcess->getLogText()); else if (currentWidget == captureProcess.get()) ekosLogOut->setPlainText(captureProcess->getLogText()); else if (currentWidget == focusProcess.get()) ekosLogOut->setPlainText(focusProcess->getLogText()); else if (currentWidget == guideProcess.get()) ekosLogOut->setPlainText(guideProcess->getLogText()); else if (currentWidget == mountProcess.get()) ekosLogOut->setPlainText(mountProcess->getLogText()); if (currentWidget == schedulerProcess.get()) ekosLogOut->setPlainText(schedulerProcess->getLogText()); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif } void Manager::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", - QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS) << text; emit newLog(text); updateLog(); } void Manager::clearLog() { - QWidget *currentWidget = toolsWidget->currentWidget(); + QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) { m_LogText.clear(); updateLog(); } else if (currentWidget == alignProcess.get()) alignProcess->clearLog(); else if (currentWidget == captureProcess.get()) captureProcess->clearLog(); else if (currentWidget == focusProcess.get()) focusProcess->clearLog(); else if (currentWidget == guideProcess.get()) guideProcess->clearLog(); else if (currentWidget == mountProcess.get()) mountProcess->clearLog(); else if (currentWidget == schedulerProcess.get()) schedulerProcess->clearLog(); } void Manager::initCapture() { if (captureProcess.get() != nullptr) return; captureProcess.reset(new Ekos::Capture()); captureProcess->setEnabled(false); int index = toolsWidget->addTab(captureProcess.get(), QIcon(":/icons/ekos_ccd.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18nc("Charge-Coupled Device", "CCD")); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } connect(captureProcess.get(), &Ekos::Capture::newLog, this, &Ekos::Manager::updateLog); connect(captureProcess.get(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus); connect(captureProcess.get(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress); - connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString &filename) { - if (Options::useSummaryPreview() && QFile::exists(filename)) - { - summaryPreview->loadFITS(filename); - } + connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString &filename) + { + if (Options::useSummaryPreview() && QFile::exists(filename)) + { + summaryPreview->loadFITS(filename); + } }); connect(captureProcess.get(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress); connect(captureProcess.get(), &Ekos::Capture::sequenceChanged, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSequence); connect(captureProcess.get(), &Ekos::Capture::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSettings); captureGroup->setEnabled(true); sequenceProgress->setEnabled(true); captureProgress->setEnabled(true); imageProgress->setEnabled(true); captureProcess->setFilterManager(filterManager); if (!capturePI) { capturePI = new QProgressIndicator(captureProcess.get()); captureStatusLayout->insertWidget(0, capturePI); } - foreach (ISD::GDInterface *device, findDevices(KSTARS_AUXILIARY)) + foreach (ISD::GDInterface * device, findDevices(KSTARS_AUXILIARY)) { if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::DUSTCAP_INTERFACE) captureProcess->setDustCap(device); if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::LIGHTBOX_INTERFACE) captureProcess->setLightBox(device); } if (managedDevices.contains(KSTARS_DOME)) { captureProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { captureProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Capture"); } void Manager::initAlign() { if (alignProcess.get() != nullptr) return; alignProcess.reset(new Ekos::Align(currentProfile)); double primaryScopeFL=0, primaryScopeAperture=0, guideScopeFL=0, guideScopeAperture=0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setEnabled(false); int index = toolsWidget->addTab(alignProcess.get(), QIcon(":/icons/ekos_align.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Align")); connect(alignProcess.get(), &Ekos::Align::newLog, this, &Ekos::Manager::updateLog); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } alignProcess->setFilterManager(filterManager); if (managedDevices.contains(KSTARS_DOME)) { alignProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { alignProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Align"); } void Manager::initFocus() { if (focusProcess.get() != nullptr) return; focusProcess.reset(new Ekos::Focus()); int index = toolsWidget->addTab(focusProcess.get(), QIcon(":/icons/ekos_focus.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Focus")); // Focus <---> Manager connections connect(focusProcess.get(), &Ekos::Focus::newLog, this, &Ekos::Manager::updateLog); connect(focusProcess.get(), &Ekos::Focus::newStatus, this, &Ekos::Manager::setFocusStatus); connect(focusProcess.get(), &Ekos::Focus::newStarPixmap, this, &Ekos::Manager::updateFocusStarPixmap); connect(focusProcess.get(), &Ekos::Focus::newProfilePixmap, this, &Ekos::Manager::updateFocusProfilePixmap); connect(focusProcess.get(), &Ekos::Focus::newHFR, this, &Ekos::Manager::updateCurrentHFR); // Focus <---> Filter Manager connections focusProcess->setFilterManager(filterManager); connect(filterManager.data(), &Ekos::FilterManager::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::newStatus, filterManager.data(), &Ekos::FilterManager::setFocusStatus, Qt::UniqueConnection); connect(filterManager.data(), &Ekos::FilterManager::newFocusOffset, focusProcess.get(), &Ekos::Focus::adjustFocusOffset, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::focusPositionAdjusted, filterManager.data(), &Ekos::FilterManager::setFocusOffsetComplete, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::absolutePositionChanged, filterManager.data(), &Ekos::FilterManager::setFocusAbsolutePosition, Qt::UniqueConnection); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } focusGroup->setEnabled(true); if (!focusPI) { focusPI = new QProgressIndicator(focusProcess.get()); focusStatusLayout->insertWidget(0, focusPI); } connectModules(); emit newModule("Focus"); } void Manager::updateCurrentHFR(double newHFR, int position) { currentHFR->setText(QString("%1").arg(newHFR, 0, 'f', 2) + " px"); - QJsonObject cStatus = { - {"hfr", newHFR}, - {"pos", position} + QJsonObject cStatus = + { + {"hfr", newHFR}, + {"pos", position} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void Manager::updateSigmas(double ra, double de) { errRA->setText(QString::number(ra, 'f', 2) + "\""); errDEC->setText(QString::number(de, 'f', 2) + "\""); QJsonObject cStatus = { {"rarms", ra}, {"derms", de} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void Manager::initMount() { if (mountProcess.get() != nullptr) return; mountProcess.reset(new Ekos::Mount()); int index = toolsWidget->addTab(mountProcess.get(), QIcon(":/icons/ekos_mount.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Mount")); connect(mountProcess.get(), &Ekos::Mount::newLog, this, &Ekos::Manager::updateLog); connect(mountProcess.get(), &Ekos::Mount::newCoords, this, &Ekos::Manager::updateMountCoords); connect(mountProcess.get(), &Ekos::Mount::newStatus, this, &Ekos::Manager::updateMountStatus); - connect(mountProcess.get(), &Ekos::Mount::newTarget, [&](const QString &target) { + connect(mountProcess.get(), &Ekos::Mount::newTarget, [&](const QString &target) + { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); - connect(mountProcess.get(), &Ekos::Mount::slewRateChanged, [&](int slewRate) { + connect(mountProcess.get(), &Ekos::Mount::slewRateChanged, [&](int slewRate) + { QJsonObject status = { { "slewRate", slewRate} }; ekosLiveClient.get()->message()->updateMountStatus(status); } - ); + ); - foreach (ISD::GDInterface *device, findDevices(KSTARS_AUXILIARY)) + foreach (ISD::GDInterface * device, findDevices(KSTARS_AUXILIARY)) { if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE) mountProcess->setGPS(device); } if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } if (!mountPI) { mountPI = new QProgressIndicator(mountProcess.get()); mountStatusLayout->insertWidget(0, mountPI); } mountGroup->setEnabled(true); connectModules(); emit newModule("Mount"); } void Manager::initGuide() { if (guideProcess.get() == nullptr) { guideProcess.reset(new Ekos::Guide()); double primaryScopeFL=0, primaryScopeAperture=0, guideScopeFL=0, guideScopeAperture=0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } //if ( (haveGuider || ccdCount > 1 || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess) == -1) if ((findDevices(KSTARS_CCD).isEmpty() == false || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess.get()) == -1) { //if (mount && mount->isConnected()) if (managedDevices.contains(KSTARS_TELESCOPE) && managedDevices[KSTARS_TELESCOPE]->isConnected()) guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); int index = toolsWidget->addTab(guideProcess.get(), QIcon(":/icons/ekos_guide.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Guide")); connect(guideProcess.get(), &Ekos::Guide::newLog, this, &Ekos::Manager::updateLog); guideGroup->setEnabled(true); if (!guidePI) { guidePI = new QProgressIndicator(guideProcess.get()); guideStatusLayout->insertWidget(0, guidePI); } connect(guideProcess.get(), &Ekos::Guide::newStatus, this, &Ekos::Manager::updateGuideStatus); connect(guideProcess.get(), &Ekos::Guide::newStarPixmap, this, &Ekos::Manager::updateGuideStarPixmap); connect(guideProcess.get(), &Ekos::Guide::newProfilePixmap, this, &Ekos::Manager::updateGuideProfilePixmap); connect(guideProcess.get(), &Ekos::Guide::newAxisSigma, this, &Ekos::Manager::updateSigmas); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } } connectModules(); emit newModule("Guide"); } void Manager::initDome() { if (domeProcess.get() != nullptr) return; domeProcess.reset(new Ekos::Dome()); - connect(domeProcess.get(), &Ekos::Dome::newStatus, [&](ISD::Dome::Status newStatus) { + connect(domeProcess.get(), &Ekos::Dome::newStatus, [&](ISD::Dome::Status newStatus) + { QJsonObject status = { { "status", ISD::Dome::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); - connect(domeProcess.get(), &Ekos::Dome::azimuthPositionChanged, [&](double pos) { + connect(domeProcess.get(), &Ekos::Dome::azimuthPositionChanged, [&](double pos) + { QJsonObject status = { { "az", pos} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); emit newModule("Dome"); ekosLiveClient->message()->sendDomes(); } void Manager::initWeather() { if (weatherProcess.get() != nullptr) return; weatherProcess.reset(new Ekos::Weather()); emit newModule("Weather"); } void Manager::initDustCap() { if (dustCapProcess.get() != nullptr) return; dustCapProcess.reset(new Ekos::DustCap()); - connect(dustCapProcess.get(), &Ekos::DustCap::newStatus, [&](ISD::DustCap::Status newStatus) { + connect(dustCapProcess.get(), &Ekos::DustCap::newStatus, [&](ISD::DustCap::Status newStatus) + { QJsonObject status = { { "status", ISD::DustCap::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); - connect(dustCapProcess.get(), &Ekos::DustCap::lightToggled, [&](bool enabled) { + connect(dustCapProcess.get(), &Ekos::DustCap::lightToggled, [&](bool enabled) + { QJsonObject status = { { "lightS", enabled} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); - connect(dustCapProcess.get(), &Ekos::DustCap::lightIntensityChanged, [&](uint16_t value) { + connect(dustCapProcess.get(), &Ekos::DustCap::lightIntensityChanged, [&](uint16_t value) + { QJsonObject status = { { "lightB", value} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); emit newModule("DustCap"); ekosLiveClient->message()->sendCaps(); } -void Manager::setST4(ISD::ST4 *st4Driver) +void Manager::setST4(ISD::ST4 * st4Driver) { appendLogText(i18n("Guider port from %1 is ready.", st4Driver->getDeviceName())); useST4 = true; initGuide(); guideProcess->addST4(st4Driver); if (Options::defaultST4Driver().isEmpty() == false) guideProcess->setST4(Options::defaultST4Driver()); } void Manager::removeTabs() { disconnect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange); for (int i = 2; i < toolsWidget->count(); i++) toolsWidget->removeTab(i); alignProcess.reset(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); mountProcess.reset(); domeProcess.reset(); weatherProcess.reset(); dustCapProcess.reset(); managedDevices.clear(); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); } bool Manager::isRunning(const QString &process) { QProcess ps; #ifdef Q_OS_OSX ps.start("pgrep", QStringList() << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.length()>0; #else ps.start("ps", QStringList() << "-o" << "comm" << "--no-headers" << "-C" << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.contains(process); #endif } -void Manager::addObjectToScheduler(SkyObject *object) +void Manager::addObjectToScheduler(SkyObject * object) { if (schedulerProcess.get() != nullptr) schedulerProcess->addObject(object); } QString Manager::getCurrentJobName() { return schedulerProcess->getCurrentJobName(); } bool Manager::setProfile(const QString &profileName) { int index = profileCombo->findText(profileName); if (index < 0) return false; profileCombo->setCurrentIndex(index); return true; } QStringList Manager::getProfiles() { QStringList profiles; for (int i = 0; i < profileCombo->count(); i++) profiles << profileCombo->itemText(i); return profiles; } void Manager::addProfile() { ProfileEditor editor(this); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } void Manager::editProfile() { ProfileEditor editor(this); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); if (editor.exec() == QDialog::Accepted) { int currentIndex = profileCombo->currentIndex(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(currentIndex); } currentProfile = getCurrentProfile(); } void Manager::deleteProfile() { currentProfile = getCurrentProfile(); if (currentProfile->name == "Simulators") return; if (KMessageBox::questionYesNo(this, i18n("Are you sure you want to delete the profile?"), i18n("Confirm Delete")) == KMessageBox::No) return; KStarsData::Instance()->userdb()->DeleteProfile(currentProfile); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); } void Manager::wizardProfile() { ProfileWizard wz; if (wz.exec() != QDialog::Accepted) return; ProfileEditor editor(this); editor.setProfileName(wz.profileName); editor.setAuxDrivers(wz.selectedAuxDrivers()); if (wz.useInternalServer == false) editor.setHostPort(wz.host, wz.port); editor.setWebManager(wz.useWebManager); editor.setGuiderType(wz.selectedExternalGuider()); // Disable connection options editor.setConnectionOptionsEnabled(false); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } -ProfileInfo *Manager::getCurrentProfile() +ProfileInfo * Manager::getCurrentProfile() { - ProfileInfo *currProfile = nullptr; + ProfileInfo * currProfile = nullptr; // Get current profile - for (auto& pi : profiles) + for (auto &pi : profiles) { if (profileCombo->currentText() == pi->name) { currProfile = pi.get(); break; } } return currProfile; } -void Manager::updateProfileLocation(ProfileInfo *pi) +void Manager::updateProfileLocation(ProfileInfo * pi) { if (pi->city.isEmpty() == false) { bool cityFound = KStars::Instance()->setGeoLocation(pi->city, pi->province, pi->country); if (cityFound) appendLogText(i18n("Site location updated to %1.", KStarsData::Instance()->geo()->fullName())); else appendLogText(i18n("Failed to update site location to %1. City not found.", KStarsData::Instance()->geo()->fullName())); } } void Manager::updateMountStatus(ISD::Telescope::Status status) { static ISD::Telescope::Status lastStatus = ISD::Telescope::MOUNT_IDLE; if (status == lastStatus) return; lastStatus = status; mountStatus->setText(dynamic_cast(managedDevices[KSTARS_TELESCOPE])->getStatusString(status)); switch (status) { - case ISD::Telescope::MOUNT_PARKING: - case ISD::Telescope::MOUNT_SLEWING: - case ISD::Telescope::MOUNT_MOVING: - mountPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); - if (mountPI->isAnimated() == false) - mountPI->startAnimation(); - break; + case ISD::Telescope::MOUNT_PARKING: + case ISD::Telescope::MOUNT_SLEWING: + case ISD::Telescope::MOUNT_MOVING: + mountPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); + if (mountPI->isAnimated() == false) + mountPI->startAnimation(); + break; - case ISD::Telescope::MOUNT_TRACKING: - mountPI->setColor(Qt::darkGreen); - if (mountPI->isAnimated() == false) - mountPI->startAnimation(); + case ISD::Telescope::MOUNT_TRACKING: + mountPI->setColor(Qt::darkGreen); + if (mountPI->isAnimated() == false) + mountPI->startAnimation(); - break; + break; - default: - if (mountPI->isAnimated()) - mountPI->stopAnimation(); + default: + if (mountPI->isAnimated()) + mountPI->stopAnimation(); } - QJsonObject cStatus = { - {"status", mountStatus->text()} + QJsonObject cStatus = + { + {"status", mountStatus->text()} }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::updateMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt) { raOUT->setText(ra); decOUT->setText(dec); azOUT->setText(az); altOUT->setText(alt); - QJsonObject cStatus = { - {"ra", dms::fromString(ra, false).Degrees()}, - {"de", dms::fromString(dec, true).Degrees()}, - {"az", dms::fromString(az, true).Degrees()}, - {"at", dms::fromString(alt, true).Degrees()}, + QJsonObject cStatus = + { + {"ra", dms::fromString(ra, false).Degrees()}, + {"de", dms::fromString(dec, true).Degrees()}, + {"az", dms::fromString(az, true).Degrees()}, + {"at", dms::fromString(alt, true).Degrees()}, }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::updateCaptureStatus(Ekos::CaptureState status) { captureStatus->setText(Ekos::getCaptureStatusString(status)); captureProgress->setValue(captureProcess->getProgressPercentage()); overallCountDown.setHMS(0, 0, 0); overallCountDown = overallCountDown.addSecs(captureProcess->getOverallRemainingTime()); sequenceCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(captureProcess->getActiveJobRemainingTime()); if (status != Ekos::CAPTURE_ABORTED && status != Ekos::CAPTURE_COMPLETE && status != Ekos::CAPTURE_IDLE) { if (status == Ekos::CAPTURE_CAPTURING) capturePI->setColor(Qt::darkGreen); else capturePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (capturePI->isAnimated() == false) { capturePI->startAnimation(); countdownTimer.start(); } } else { if (capturePI->isAnimated()) { capturePI->stopAnimation(); countdownTimer.stop(); if (focusStatus->text() == "Complete") { if (focusPI->isAnimated()) focusPI->stopAnimation(); } imageProgress->setValue(0); sequenceLabel->setText(i18n("Sequence")); imageRemainingTime->setText("--:--:--"); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); } } - QJsonObject cStatus = { + QJsonObject cStatus = + { {"status", captureStatus->text()}, {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(cStatus); } -void Manager::updateCaptureProgress(Ekos::SequenceJob *job) +void Manager::updateCaptureProgress(Ekos::SequenceJob * job) { // Image is set to nullptr only on initial capture start up int completed = job->getCompleted(); // if (job->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) // completed = job->getCompleted() + 1; // else // completed = job->isPreview() ? job->getCompleted() : job->getCompleted() + 1; if (job->isPreview() == false) { sequenceLabel->setText(QString("Job # %1/%2 %3 (%4/%5)") .arg(captureProcess->getActiveJobID() + 1) .arg(captureProcess->getJobCount()) .arg(job->getFullPrefix()) .arg(completed) .arg(job->getCount())); } else sequenceLabel->setText(i18n("Preview")); sequenceProgress->setRange(0, job->getCount()); sequenceProgress->setValue(completed); - QJsonObject status = { + QJsonObject status = + { {"seqv", completed}, {"seqr", job->getCount()}, {"seql", sequenceLabel->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); if (job->getStatus() == SequenceJob::JOB_BUSY) { QString uuid = QUuid::createUuid().toString(); uuid = uuid.remove(QRegularExpression("[-{}]")); - FITSView *image = job->getActiveChip()->getImageView(FITS_NORMAL); - ekosLiveClient.get()->media()->sendPreviewImage(image, uuid); - ekosLiveClient.get()->cloud()->sendPreviewImage(image, uuid); +// FITSView *image = job->getActiveChip()->getImageView(FITS_NORMAL); +// ekosLiveClient.get()->media()->sendPreviewImage(image, uuid); +// ekosLiveClient.get()->cloud()->sendPreviewImage(image, uuid); + QString filename = job->property("filename").toString(); + ekosLiveClient.get()->media()->sendPreviewImage(filename, uuid); + if (job->isPreview() == false) + ekosLiveClient.get()->cloud()->sendPreviewImage(filename, uuid); + } } -void Manager::updateExposureProgress(Ekos::SequenceJob *job) +void Manager::updateExposureProgress(Ekos::SequenceJob * job) { imageCountDown.setHMS(0, 0, 0); imageCountDown = imageCountDown.addSecs(job->getExposeLeft()); if (imageCountDown.hour() == 23) imageCountDown.setHMS(0, 0, 0); imageProgress->setRange(0, job->getExposure()); imageProgress->setValue(job->getExposeLeft()); imageRemainingTime->setText(imageCountDown.toString("hh:mm:ss")); - QJsonObject status { + QJsonObject status + { {"expv", job->getExposeLeft()}, {"expr", job->getExposure()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void Manager::updateCaptureCountDown() { overallCountDown = overallCountDown.addSecs(-1); if (overallCountDown.hour() == 23) overallCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(-1); if (sequenceCountDown.hour() == 23) sequenceCountDown.setHMS(0, 0, 0); overallRemainingTime->setText(overallCountDown.toString("hh:mm:ss")); sequenceRemainingTime->setText(sequenceCountDown.toString("hh:mm:ss")); - QJsonObject status = { - {"seqt", sequenceRemainingTime->text()}, - {"ovt", overallRemainingTime->text()} + QJsonObject status = + { + {"seqt", sequenceRemainingTime->text()}, + {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void Manager::updateFocusStarPixmap(QPixmap &starPixmap) { if (starPixmap.isNull()) return; focusStarPixmap.reset(new QPixmap(starPixmap)); focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), - Qt::KeepAspectRatio, Qt::SmoothTransformation)); + Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::updateFocusProfilePixmap(QPixmap &profilePixmap) { if (profilePixmap.isNull()) return; focusProfileImage->setPixmap(profilePixmap); } void Manager::setFocusStatus(Ekos::FocusState status) { focusStatus->setText(Ekos::getFocusStatusString(status)); if (status >= Ekos::FOCUS_PROGRESS) { focusPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else if (status == Ekos::FOCUS_COMPLETE && Options::enforceAutofocus() && captureProcess->getActiveJobID() != -1) { focusPI->setColor(Qt::darkGreen); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else { if (focusPI->isAnimated()) focusPI->stopAnimation(); } - QJsonObject cStatus = { - {"status", focusStatus->text()} + QJsonObject cStatus = + { + {"status", focusStatus->text()} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void Manager::updateGuideStatus(Ekos::GuideState status) { guideStatus->setText(Ekos::getGuideStatusString(status)); switch (status) { - case Ekos::GUIDE_IDLE: - case Ekos::GUIDE_CALIBRATION_ERROR: - case Ekos::GUIDE_ABORTED: - case Ekos::GUIDE_SUSPENDED: - case Ekos::GUIDE_DITHERING_ERROR: - case Ekos::GUIDE_CALIBRATION_SUCESS: - if (guidePI->isAnimated()) - guidePI->stopAnimation(); - break; - - case Ekos::GUIDE_CALIBRATING: - guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); - if (guidePI->isAnimated() == false) - guidePI->startAnimation(); - break; - case Ekos::GUIDE_GUIDING: - guidePI->setColor(Qt::darkGreen); - if (guidePI->isAnimated() == false) - guidePI->startAnimation(); - break; - case Ekos::GUIDE_DITHERING: - guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); - if (guidePI->isAnimated() == false) - guidePI->startAnimation(); - break; - case Ekos::GUIDE_DITHERING_SUCCESS: - guidePI->setColor(Qt::darkGreen); - if (guidePI->isAnimated() == false) - guidePI->startAnimation(); - break; - - default: - if (guidePI->isAnimated()) - guidePI->stopAnimation(); - break; - } - - QJsonObject cStatus = { - {"status", guideStatus->text()} + case Ekos::GUIDE_IDLE: + case Ekos::GUIDE_CALIBRATION_ERROR: + case Ekos::GUIDE_ABORTED: + case Ekos::GUIDE_SUSPENDED: + case Ekos::GUIDE_DITHERING_ERROR: + case Ekos::GUIDE_CALIBRATION_SUCESS: + if (guidePI->isAnimated()) + guidePI->stopAnimation(); + break; + + case Ekos::GUIDE_CALIBRATING: + guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); + if (guidePI->isAnimated() == false) + guidePI->startAnimation(); + break; + case Ekos::GUIDE_GUIDING: + guidePI->setColor(Qt::darkGreen); + if (guidePI->isAnimated() == false) + guidePI->startAnimation(); + break; + case Ekos::GUIDE_DITHERING: + guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); + if (guidePI->isAnimated() == false) + guidePI->startAnimation(); + break; + case Ekos::GUIDE_DITHERING_SUCCESS: + guidePI->setColor(Qt::darkGreen); + if (guidePI->isAnimated() == false) + guidePI->startAnimation(); + break; + + default: + if (guidePI->isAnimated()) + guidePI->stopAnimation(); + break; + } + + QJsonObject cStatus = + { + {"status", guideStatus->text()} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void Manager::updateGuideStarPixmap(QPixmap &starPix) { if (starPix.isNull()) return; guideStarPixmap.reset(new QPixmap(starPix)); guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), - Qt::KeepAspectRatio, Qt::SmoothTransformation)); + Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void Manager::updateGuideProfilePixmap(QPixmap &profilePix) { if (profilePix.isNull()) return; guideProfileImage->setPixmap(profilePix); } -void Manager::setTarget(SkyObject *o) +void Manager::setTarget(SkyObject * o) { mountTarget->setText(o->name()); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", o->name()}})); } void Manager::showEkosOptions() { - QWidget *currentWidget = toolsWidget->currentWidget(); + QWidget * currentWidget = toolsWidget->currentWidget(); if (alignProcess.get() && alignProcess.get() == currentWidget) { - KConfigDialog *alignSettings = KConfigDialog::exists("alignsettings"); + KConfigDialog * alignSettings = KConfigDialog::exists("alignsettings"); if (alignSettings) { alignSettings->setEnabled(true); alignSettings->show(); } return; } if (guideProcess.get() && guideProcess.get() == currentWidget) { KConfigDialog::showDialog("guidesettings"); return; } if (ekosOptionsWidget == nullptr) { optionsB->click(); } else if (KConfigDialog::showDialog("settings")) { - KConfigDialog *cDialog = KConfigDialog::exists("settings"); + KConfigDialog * cDialog = KConfigDialog::exists("settings"); cDialog->setCurrentPage(ekosOptionsWidget); } } void Manager::getCurrentProfileTelescopeInfo(double &primaryFocalLength, double &primaryAperture, double &guideFocalLength, double &guideAperture) { - ProfileInfo *pi = getCurrentProfile(); + ProfileInfo * pi = getCurrentProfile(); if (pi) { int primaryScopeID=0, guideScopeID=0; primaryScopeID=pi->primaryscope; guideScopeID=pi->guidescope; if (primaryScopeID > 0 || guideScopeID > 0) { // Get all OAL equipment filter list - QList m_scopeList; + QList m_scopeList; KStarsData::Instance()->userdb()->GetAllScopes(m_scopeList); - foreach(OAL::Scope *oneScope, m_scopeList) + foreach(OAL::Scope * oneScope, m_scopeList) { if (oneScope->id().toInt() == primaryScopeID) { primaryFocalLength = oneScope->focalLength(); primaryAperture = oneScope->aperture(); } if (oneScope->id().toInt() == guideScopeID) { guideFocalLength = oneScope->focalLength(); guideAperture = oneScope->aperture(); } } } } } void Manager::updateDebugInterfaces() { KSUtils::Logging::SyncFilterRules(); - for (ISD::GDInterface *device : genericDevices) + for (ISD::GDInterface * device : genericDevices) { - INDI::Property *debugProp = device->getProperty("DEBUG"); - ISwitchVectorProperty *debugSP = nullptr; + INDI::Property * debugProp = device->getProperty("DEBUG"); + ISwitchVectorProperty * debugSP = nullptr; if (debugProp) debugSP = debugProp->getSwitch(); else continue; // Check if the debug interface matches the driver device class if ( ( opsLogs->getINDIDebugInterface() & device->getBaseDevice()->getDriverInterface() ) && - debugSP->sp[0].s != ISS_ON) + debugSP->sp[0].s != ISS_ON) { debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Enabling debug logging for %1...", device->getDeviceName())); } else if ( !( opsLogs->getINDIDebugInterface() & device->getBaseDevice()->getDriverInterface() ) && debugSP->sp[0].s != ISS_OFF) { debugSP->sp[0].s = ISS_OFF; debugSP->sp[1].s = ISS_ON; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Disabling debug logging for %1...", device->getDeviceName())); } if (opsLogs->isINDISettingsChanged()) device->setConfig(SAVE_CONFIG); } } -void Manager::watchDebugProperty(ISwitchVectorProperty *svp) +void Manager::watchDebugProperty(ISwitchVectorProperty * svp) { if (!strcmp(svp->name, "DEBUG")) { - ISD::GenericDevice *deviceInterface = qobject_cast(sender()); + ISD::GenericDevice * deviceInterface = qobject_cast(sender()); // We don't process pure general interfaces if (deviceInterface->getBaseDevice()->getDriverInterface() == INDI::BaseDevice::GENERAL_INTERFACE) return; // If debug was turned off, but our logging policy requires it then turn it back on. // We turn on debug logging if AT LEAST one driver interface is selected by the logging settings if (svp->s == IPS_OK && svp->sp[0].s == ISS_OFF && (opsLogs->getINDIDebugInterface() & deviceInterface->getBaseDevice()->getDriverInterface())) { svp->sp[0].s = ISS_ON; svp->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-enabling debug logging for %1...", deviceInterface->getDeviceName())); } // To turn off debug logging, NONE of the driver interfaces should be enabled in logging settings. // For example, if we have CCD+FilterWheel device and CCD + Filter Wheel logging was turned on in // the log settings, then if the user turns off only CCD logging, the debug logging is NOT // turned off until he turns off Filter Wheel logging as well. else if (svp->s == IPS_OK && svp->sp[0].s == ISS_ON && !(opsLogs->getINDIDebugInterface() & deviceInterface->getBaseDevice()->getDriverInterface())) { svp->sp[0].s = ISS_OFF; svp->sp[1].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-disabling debug logging for %1...", deviceInterface->getDeviceName())); } } } void Manager::announceEvent(const QString &message, KSNotification::EventType event) { ekosLiveClient.get()->message()->sendEvent(message, event); } void Manager::connectModules() { // Guide <---> Capture connections if (captureProcess.get() && guideProcess.get()) { captureProcess.get()->disconnect(guideProcess.get()); guideProcess.get()->disconnect(captureProcess.get()); // Guide Limits connect(guideProcess.get(), &Ekos::Guide::newStatus, captureProcess.get(), &Ekos::Capture::setGuideStatus, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::newAxisDelta, captureProcess.get(), &Ekos::Capture::setGuideDeviation); // Dithering connect(captureProcess.get(), &Ekos::Capture::newStatus, guideProcess.get(), &Ekos::Guide::setCaptureStatus, Qt::UniqueConnection); // Guide Head connect(captureProcess.get(), &Ekos::Capture::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::guideChipUpdated, captureProcess.get(), &Ekos::Capture::setGuideChip, Qt::UniqueConnection); // Meridian Flip connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, guideProcess.get(), &Ekos::Guide::abort, Qt::UniqueConnection); - connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, guideProcess.get(), [&]() { + connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, guideProcess.get(), [&]() + { if (Options::resetGuideCalibration()) guideProcess->clearCalibration(); guideProcess->guide(); }); } // Guide <---> Mount connections if (guideProcess.get() && mountProcess.get()) { // Parking connect(mountProcess.get(), &Ekos::Mount::newStatus, guideProcess.get(), &Ekos::Guide::setMountStatus, Qt::UniqueConnection); } // Focus <---> Guide connections if (guideProcess.get() && focusProcess.get()) { // Suspend connect(focusProcess.get(), &Ekos::Focus::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); } // Capture <---> Focus connections if (captureProcess.get() && focusProcess.get()) { // Check focus HFR value connect(captureProcess.get(), &Ekos::Capture::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); // Reset Focus connect(captureProcess.get(), &Ekos::Capture::resetFocus, focusProcess.get(), &Ekos::Focus::resetFrame, Qt::UniqueConnection); // New Focus Status connect(focusProcess.get(), &Ekos::Focus::newStatus, captureProcess.get(), &Ekos::Capture::setFocusStatus, Qt::UniqueConnection); // New Focus HFR connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); } // Capture <---> Align connections if (captureProcess.get() && alignProcess.get()) { // Alignment flag connect(alignProcess.get(), &Ekos::Align::newStatus, captureProcess.get(), &Ekos::Capture::setAlignStatus, Qt::UniqueConnection); // Solver data connect(alignProcess.get(), &Ekos::Align::newSolverResults, captureProcess.get(), &Ekos::Capture::setAlignResults, Qt::UniqueConnection); // Capture Status connect(captureProcess.get(), &Ekos::Capture::newStatus, alignProcess.get(), &Ekos::Align::setCaptureStatus, Qt::UniqueConnection); } // Capture <---> Mount connections if (captureProcess.get() && mountProcess.get()) { // Meridian Flip states connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, mountProcess.get(), &Ekos::Mount::disableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, mountProcess.get(), &Ekos::Mount::enableAltLimits, Qt::UniqueConnection); // Mount Status connect(mountProcess.get(), &Ekos::Mount::newStatus, captureProcess.get(), &Ekos::Capture::setMountStatus, Qt::UniqueConnection); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Focus <---> Mount connections if (focusProcess.get() && mountProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, focusProcess.get(), &Ekos::Focus::setMountStatus, Qt::UniqueConnection); } // Mount <---> Align connections if (mountProcess.get() && alignProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, alignProcess.get(), &Ekos::Align::setMountStatus, Qt::UniqueConnection); } // Mount <---> Guide connections if (mountProcess.get() && guideProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::pierSideChanged, guideProcess.get(), &Ekos::Guide::setPierSide, Qt::UniqueConnection); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Align <--> EkosLive connections if (alignProcess.get() && ekosLiveClient.get()) { alignProcess.get()->disconnect(ekosLiveClient.get()); connect(alignProcess.get(), &Ekos::Align::newStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignStatus); connect(alignProcess.get(), &Ekos::Align::newSolution, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignSolution); connect(alignProcess.get(), &Ekos::Align::newPAHStage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHStage); connect(alignProcess.get(), &Ekos::Align::newPAHMessage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHMessage); connect(alignProcess.get(), &Ekos::Align::PAHEnabled, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHEnabled); connect(alignProcess.get(), &Ekos::Align::newImage, [&](FITSView *view) { ekosLiveClient.get()->media()->sendPreviewImage(view, QString()); }); connect(alignProcess.get(), &Ekos::Align::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendUpdatedFrame); connect(alignProcess.get(), &Ekos::Align::polarResultUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::setPolarResults); connect(alignProcess.get(), &Ekos::Align::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendAlignSettings); connect(alignProcess.get(), &Ekos::Align::newCorrectionVector, ekosLiveClient.get()->media(), &EkosLive::Media::setCorrectionVector); } } }