File indexing completed on 2024-04-28 03:43:18
0001 /* 0002 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "sequencejob.h" 0008 0009 #include <KNotifications/KNotification> 0010 #include <ekos_capture_debug.h> 0011 #include "capturedeviceadaptor.h" 0012 #include "skyobjects/skypoint.h" 0013 #include "ksnotification.h" 0014 0015 #define MF_TIMER_TIMEOUT 90000 0016 #define MF_RA_DIFF_LIMIT 4 0017 0018 namespace Ekos 0019 { 0020 QString const &SequenceJob::ISOMarker("_ISO8601"); 0021 0022 const QStringList SequenceJob::StatusStrings() 0023 { 0024 static const QStringList names = {i18n("Idle"), i18n("In Progress"), i18n("Error"), i18n("Aborted"), 0025 i18n("Complete") 0026 }; 0027 return names; 0028 } 0029 0030 0031 /** 0032 * @brief SequenceJob::SequenceJob Construct job from XML source 0033 * @param root pointer to valid job stored in XML format. 0034 */ 0035 SequenceJob::SequenceJob(XMLEle * root, QString targetName) 0036 { 0037 // set own unconnected state machine 0038 QSharedPointer<CaptureModuleState> sharedState; 0039 sharedState.reset(new CaptureModuleState); 0040 state.reset(new SequenceJobState(sharedState)); 0041 // set simple device adaptor 0042 devices.reset(new CaptureDeviceAdaptor()); 0043 0044 init(SequenceJob::JOBTYPE_BATCH, root, sharedState, targetName); 0045 } 0046 0047 SequenceJob::SequenceJob(const QSharedPointer<CaptureDeviceAdaptor> cp, 0048 const QSharedPointer<CaptureModuleState> sharedState, 0049 SequenceJobType jobType, XMLEle *root, QString targetName) 0050 { 0051 devices = cp; 0052 init(jobType, root, sharedState, targetName); 0053 } 0054 0055 void Ekos::SequenceJob::init(SequenceJobType jobType, XMLEle *root, 0056 QSharedPointer<CaptureModuleState> sharedState, 0057 const QString &targetName) 0058 { 0059 // initialize the state machine 0060 state.reset(new SequenceJobState(sharedState)); 0061 0062 loadFrom(root, targetName, jobType, sharedState); 0063 0064 // signal forwarding between this and the state machine 0065 connect(state.data(), &SequenceJobState::prepareState, this, &SequenceJob::prepareState); 0066 connect(state.data(), &SequenceJobState::prepareComplete, this, &SequenceJob::processPrepareComplete); 0067 connect(state.data(), &SequenceJobState::abortCapture, this, &SequenceJob::processAbortCapture); 0068 connect(state.data(), &SequenceJobState::newLog, this, &SequenceJob::newLog); 0069 // start capturing as soon as the capture initialization is complete 0070 connect(state.data(), &SequenceJobState::initCaptureComplete, this, &SequenceJob::capture); 0071 0072 // finish if XML document empty 0073 if (root == nullptr) 0074 return; 0075 0076 // create signature with current target 0077 auto placeholderPath = Ekos::PlaceholderPath(); 0078 placeholderPath.processJobInfo(this); 0079 } 0080 0081 void SequenceJob::resetStatus(JOBStatus status) 0082 { 0083 setStatus(status); 0084 setCalibrationStage(SequenceJobState::CAL_NONE); 0085 switch (status) 0086 { 0087 case JOB_IDLE: 0088 setCompleted(0); 0089 // 2022.03.10: Keeps failing on Windows despite installing latest libindi 0090 #ifndef Q_OS_WIN 0091 INDI_FALLTHROUGH; 0092 #endif 0093 case JOB_ERROR: 0094 case JOB_ABORTED: 0095 case JOB_DONE: 0096 m_ExposeLeft = 0; 0097 m_CaptureRetires = 0; 0098 m_JobProgressIgnored = false; 0099 break; 0100 case JOB_BUSY: 0101 // do nothing 0102 break; 0103 } 0104 } 0105 0106 void SequenceJob::abort() 0107 { 0108 setStatus(JOB_ABORTED); 0109 if (devices.data()->getActiveChip()) 0110 { 0111 if (devices.data()->getActiveChip()->canAbort()) 0112 devices.data()->getActiveChip()->abortExposure(); 0113 devices.data()->getActiveChip()->setBatchMode(false); 0114 } 0115 } 0116 0117 void SequenceJob::done() 0118 { 0119 setStatus(JOB_DONE); 0120 } 0121 0122 int SequenceJob::getJobRemainingTime(double estimatedDownloadTime) 0123 { 0124 double remaining = (getCoreProperty(SJ_Exposure).toDouble() + 0125 estimatedDownloadTime + 0126 getCoreProperty(SJ_Delay).toDouble() / 1000) * 0127 (getCoreProperty(SJ_Count).toDouble() - getCompleted()); 0128 0129 if (getStatus() == JOB_BUSY) 0130 { 0131 if (getExposeLeft() > 0.0) 0132 remaining -= getCoreProperty(SJ_Exposure).toDouble() - getExposeLeft(); 0133 else 0134 remaining += getExposeLeft() + estimatedDownloadTime; 0135 } 0136 0137 return static_cast<int>(std::round(remaining)); 0138 } 0139 0140 void SequenceJob::setStatus(JOBStatus const in_status) 0141 { 0142 state->reset(in_status); 0143 } 0144 0145 void SequenceJob::setISO(int index) 0146 { 0147 if (devices->getActiveChip()) 0148 { 0149 setCoreProperty(SequenceJob::SJ_ISOIndex, index); 0150 const auto isolist = devices->getActiveChip()->getISOList(); 0151 if (isolist.count() > index && index >= 0) 0152 setCoreProperty(SequenceJob::SJ_ISO, isolist[index]); 0153 } 0154 } 0155 0156 QStringList SequenceJob::frameTypes() const 0157 { 0158 if (!devices->getActiveCamera()) 0159 return QStringList({"Light", "Bias", "Dark", "Flat"}); 0160 0161 ISD::CameraChip *tChip = devices->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD); 0162 0163 return tChip->getFrameTypes(); 0164 } 0165 0166 QStringList SequenceJob::filterLabels() const 0167 { 0168 if (devices->getFilterManager().isNull()) 0169 return QStringList(); 0170 0171 return devices->getFilterManager()->getFilterLabels(); 0172 0173 } 0174 0175 void SequenceJob::connectDeviceAdaptor() 0176 { 0177 devices->setCurrentSequenceJobState(state); 0178 // connect state machine with device adaptor 0179 connect(state.data(), &SequenceJobState::readCurrentState, devices.data(), 0180 &CaptureDeviceAdaptor::readCurrentState); 0181 connect(state.data(), &SequenceJobState::flatSyncFocus, devices.data(), 0182 &CaptureDeviceAdaptor::flatSyncFocus); 0183 // connect device adaptor with state machine 0184 connect(devices.data(), &CaptureDeviceAdaptor::flatSyncFocusChanged, state.data(), 0185 &SequenceJobState::flatSyncFocusChanged); 0186 } 0187 0188 void SequenceJob::disconnectDeviceAdaptor() 0189 { 0190 devices->disconnectDevices(state.data()); 0191 disconnect(state.data(), &SequenceJobState::readCurrentState, devices.data(), 0192 &CaptureDeviceAdaptor::readCurrentState); 0193 disconnect(state.data(), &SequenceJobState::flatSyncFocus, devices.data(), 0194 &CaptureDeviceAdaptor::flatSyncFocus); 0195 disconnect(devices.data(), &CaptureDeviceAdaptor::flatSyncFocusChanged, state.data(), 0196 &SequenceJobState::flatSyncFocusChanged); 0197 } 0198 0199 void SequenceJob::startCapturing(bool autofocusReady, FITSMode mode) 0200 { 0201 state->initCapture(getFrameType(), jobType() == SequenceJob::JOBTYPE_PREVIEW, autofocusReady, mode); 0202 } 0203 0204 void SequenceJob::capture(FITSMode mode) 0205 { 0206 if (!devices.data()->getActiveCamera() || !devices.data()->getActiveChip()) 0207 return; 0208 0209 // initialize the log entry 0210 QString logentry = QString("Capture exposure = %1 sec, type = %2").arg(getCoreProperty(SJ_Exposure).toDouble()).arg( 0211 CCDFrameTypeNames[getFrameType()]); 0212 logentry.append(QString(", filter = %1, upload mode = %2").arg(getCoreProperty(SJ_Filter).toString()).arg(getUploadMode())); 0213 0214 devices.data()->getActiveChip()->setBatchMode(jobType() != SequenceJob::JOBTYPE_PREVIEW); 0215 devices.data()->getActiveCamera()->setSeqPrefix(getCoreProperty(SJ_FullPrefix).toString()); 0216 logentry.append(QString(", batch mode = %1, seq prefix = %2").arg(jobType() != SequenceJob::JOBTYPE_PREVIEW ? "true" : 0217 "false").arg(getCoreProperty(SJ_FullPrefix).toString())); 0218 0219 if (jobType() == SequenceJob::JOBTYPE_PREVIEW) 0220 { 0221 if (devices.data()->getActiveCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT) 0222 devices.data()->getActiveCamera()->setUploadMode(ISD::Camera::UPLOAD_CLIENT); 0223 } 0224 else 0225 devices.data()->getActiveCamera()->setUploadMode(m_UploadMode); 0226 0227 QMapIterator<QString, QMap<QString, QVariant>> i(m_CustomProperties); 0228 while (i.hasNext()) 0229 { 0230 i.next(); 0231 auto customProp = devices.data()->getActiveCamera()->getProperty(i.key()); 0232 if (customProp) 0233 { 0234 QMap<QString, QVariant> elements = i.value(); 0235 QMapIterator<QString, QVariant> j(elements); 0236 0237 switch (customProp.getType()) 0238 { 0239 case INDI_SWITCH: 0240 { 0241 auto sp = customProp.getSwitch(); 0242 while (j.hasNext()) 0243 { 0244 j.next(); 0245 auto oneSwitch = sp->findWidgetByName(j.key().toLatin1().data()); 0246 if (oneSwitch) 0247 oneSwitch->setState(static_cast<ISState>(j.value().toInt())); 0248 } 0249 devices.data()->getActiveCamera()->sendNewProperty(sp); 0250 } 0251 break; 0252 case INDI_TEXT: 0253 { 0254 auto tp = customProp.getText(); 0255 while (j.hasNext()) 0256 { 0257 j.next(); 0258 auto oneText = tp->findWidgetByName(j.key().toLatin1().data()); 0259 if (oneText) 0260 oneText->setText(j.value().toString().toLatin1().constData()); 0261 } 0262 devices.data()->getActiveCamera()->sendNewProperty(tp); 0263 } 0264 break; 0265 case INDI_NUMBER: 0266 { 0267 auto np = customProp.getNumber(); 0268 while (j.hasNext()) 0269 { 0270 j.next(); 0271 auto oneNumber = np->findWidgetByName(j.key().toLatin1().data()); 0272 if (oneNumber) 0273 oneNumber->setValue(j.value().toDouble()); 0274 } 0275 devices.data()->getActiveCamera()->sendNewProperty(np); 0276 } 0277 break; 0278 default: 0279 continue; 0280 } 0281 } 0282 } 0283 0284 const auto remoteFormatDirectory = getCoreProperty(SJ_RemoteFormatDirectory).toString(); 0285 const auto remoteFormatFilename = getCoreProperty(SJ_RemoteFormatFilename).toString(); 0286 if (devices.data()->getActiveChip()->isBatchMode() && 0287 remoteFormatDirectory.isEmpty() == false && 0288 remoteFormatFilename.isEmpty() == false) 0289 { 0290 devices.data()->getActiveCamera()->updateUploadSettings(remoteFormatDirectory, remoteFormatFilename); 0291 if (getUploadMode() != ISD::Camera::UPLOAD_CLIENT) 0292 logentry.append(QString(", remote dir = %1, remote format = %2").arg(remoteFormatDirectory).arg(remoteFormatFilename)); 0293 } 0294 0295 const int ISOIndex = getCoreProperty(SJ_ISOIndex).toInt(); 0296 if (ISOIndex != -1) 0297 { 0298 logentry.append(QString(", ISO index = %1").arg(ISOIndex)); 0299 if (ISOIndex != devices.data()->getActiveChip()->getISOIndex()) 0300 devices.data()->getActiveChip()->setISOIndex(ISOIndex); 0301 } 0302 0303 const auto gain = getCoreProperty(SJ_Gain).toDouble(); 0304 if (gain >= 0) 0305 { 0306 logentry.append(QString(", gain = %1").arg(gain)); 0307 devices.data()->getActiveCamera()->setGain(gain); 0308 } 0309 0310 const auto offset = getCoreProperty(SJ_Offset).toDouble(); 0311 if (offset >= 0) 0312 { 0313 logentry.append(QString(", offset = %1").arg(offset)); 0314 devices.data()->getActiveCamera()->setOffset(offset); 0315 } 0316 0317 devices.data()->getActiveCamera()->setCaptureFormat(getCoreProperty(SJ_Format).toString()); 0318 devices.data()->getActiveCamera()->setEncodingFormat(getCoreProperty(SJ_Encoding).toString()); 0319 devices.data()->getActiveChip()->setFrameType(getFrameType()); 0320 logentry.append(QString(", format = %1, encoding = %2").arg(getCoreProperty(SJ_Format).toString()).arg(getCoreProperty( 0321 SJ_Encoding).toString())); 0322 0323 // Only attempt to set ROI and Binning if CCD transfer format is FITS or XISF 0324 int currentBinX = 1, currentBinY = 1; 0325 devices.data()->getActiveChip()->getBinning(¤tBinX, ¤tBinY); 0326 0327 const auto binning = getCoreProperty(SJ_Binning).toPoint(); 0328 // N.B. Always set binning _before_ setting frame because if the subframed image 0329 // is problematic in 1x1 but works fine for 2x2, then it would fail it was set first 0330 // So setting binning first always ensures this will work. 0331 if (devices.data()->getActiveChip()->canBin()) 0332 { 0333 if (devices.data()->getActiveChip()->setBinning(binning.x(), binning.y()) == false) 0334 { 0335 qCWarning(KSTARS_EKOS_CAPTURE()) << "Cannot set binning to " << "x =" << binning.x() << ", y =" << binning.y(); 0336 setStatus(JOB_ERROR); 0337 emit captureStarted(CaptureModuleState::CAPTURE_BIN_ERROR); 0338 } 0339 else 0340 logentry.append(QString(", binning = %1x%2").arg(binning.x()).arg(binning.y())); 0341 } 0342 else 0343 logentry.append(QString(", Cannot bin")); 0344 0345 0346 const auto roi = getCoreProperty(SJ_ROI).toRect(); 0347 0348 if (devices.data()->getActiveChip()->canSubframe()) 0349 { 0350 if ((roi.width() > 0 && roi.height() > 0) && devices.data()->getActiveChip()->setFrame(roi.x(), 0351 roi.y(), 0352 roi.width(), 0353 roi.height(), 0354 currentBinX != binning.x()) == false) 0355 { 0356 qCWarning(KSTARS_EKOS_CAPTURE()) << "Cannot set ROI to " << "x =" << roi.x() << ", y =" << roi.y() << ", widht =" << 0357 roi.width() << "height =" << roi.height(); 0358 setStatus(JOB_ERROR); 0359 emit captureStarted(CaptureModuleState::CAPTURE_FRAME_ERROR); 0360 } 0361 else 0362 logentry.append(QString(", ROI = (%1+%2, %3+%4)").arg(roi.x()).arg(roi.width()).arg(roi.y()).arg(roi.width())); 0363 } 0364 else 0365 logentry.append(", Cannot subframe"); 0366 0367 // In case FITS Viewer is not enabled. Then for flat frames, we still need to keep the data 0368 // otherwise INDI CCD would simply discard loading the data in batch mode as the data are already 0369 // saved to disk and since no extra processing is required, FITSData is not loaded up with the data. 0370 // But in case of automatically calculated flat frames, we need FITSData. 0371 // Therefore, we need to explicitly set mode to FITS_CALIBRATE so that FITSData is generated. 0372 devices.data()->getActiveChip()->setCaptureMode(mode); 0373 devices.data()->getActiveChip()->setCaptureFilter(FITS_NONE); 0374 0375 setStatus(getStatus()); 0376 0377 const auto exposure = getCoreProperty(SJ_Exposure).toDouble(); 0378 m_ExposeLeft = exposure; 0379 devices.data()->getActiveChip()->capture(exposure); 0380 // create log entry with settings 0381 qCInfo(KSTARS_EKOS_CAPTURE()) << logentry; 0382 0383 emit captureStarted(CaptureModuleState::CAPTURE_OK); 0384 } 0385 0386 void SequenceJob::setTargetFilter(int pos, const QString &name) 0387 { 0388 state->targetFilterID = pos; 0389 setCoreProperty(SJ_Filter, name); 0390 } 0391 0392 double SequenceJob::getExposeLeft() const 0393 { 0394 return m_ExposeLeft; 0395 } 0396 0397 void SequenceJob::setExposeLeft(double value) 0398 { 0399 m_ExposeLeft = value; 0400 } 0401 0402 0403 int SequenceJob::getCaptureRetires() const 0404 { 0405 return m_CaptureRetires; 0406 } 0407 0408 void SequenceJob::setCaptureRetires(int value) 0409 { 0410 m_CaptureRetires = value; 0411 } 0412 0413 int SequenceJob::getCurrentFilter() const 0414 { 0415 return state->m_CaptureModuleState->currentFilterID; 0416 } 0417 0418 ISD::Mount::PierSide SequenceJob::getPierSide() const 0419 { 0420 return state->m_CaptureModuleState->getPierSide(); 0421 } 0422 0423 // Setter: Set upload mode 0424 void SequenceJob::setUploadMode(ISD::Camera::UploadMode value) 0425 { 0426 m_UploadMode = value; 0427 } 0428 // Getter: get upload mode 0429 ISD::Camera::UploadMode SequenceJob::getUploadMode() const 0430 { 0431 return m_UploadMode; 0432 } 0433 0434 // Setter: Set flat field source 0435 void SequenceJob::setCalibrationPreAction(uint32_t value) 0436 { 0437 state->m_CalibrationPreAction = value; 0438 } 0439 // Getter: Get calibration pre action 0440 uint32_t SequenceJob::getCalibrationPreAction() const 0441 { 0442 return state->m_CalibrationPreAction; 0443 } 0444 0445 void SequenceJob::setWallCoord(const SkyPoint &value) 0446 { 0447 state->wallCoord = value; 0448 } 0449 0450 const SkyPoint &SequenceJob::getWallCoord() const 0451 { 0452 return state->wallCoord; 0453 } 0454 0455 // Setter: Set flat field duration 0456 void SequenceJob::setFlatFieldDuration(FlatFieldDuration value) 0457 { 0458 m_FlatFieldDuration = value; 0459 } 0460 0461 // Getter: Get flat field duration 0462 FlatFieldDuration SequenceJob::getFlatFieldDuration() const 0463 { 0464 return m_FlatFieldDuration; 0465 } 0466 0467 void SequenceJob::setJobProgressIgnored(bool value) 0468 { 0469 m_JobProgressIgnored = value; 0470 } 0471 0472 bool SequenceJob::getJobProgressIgnored() const 0473 { 0474 return m_JobProgressIgnored; 0475 } 0476 0477 void SequenceJob::updateDeviceStates() 0478 { 0479 setLightBox(devices->lightBox()); 0480 addMount(devices->mount()); 0481 setDome(devices->dome()); 0482 setDustCap(devices->dustCap()); 0483 } 0484 0485 void SequenceJob::setLightBox(ISD::LightBox * lightBox) 0486 { 0487 state->m_CaptureModuleState->hasLightBox = (lightBox != nullptr); 0488 } 0489 0490 void SequenceJob::setDustCap(ISD::DustCap * dustCap) 0491 { 0492 state->m_CaptureModuleState->hasDustCap = (dustCap != nullptr); 0493 } 0494 0495 void SequenceJob::addMount(ISD::Mount * scope) 0496 { 0497 state->m_CaptureModuleState->hasTelescope = (scope != nullptr); 0498 } 0499 0500 void SequenceJob::setDome(ISD::Dome * dome) 0501 { 0502 state->m_CaptureModuleState->hasDome = (dome != nullptr); 0503 } 0504 0505 double SequenceJob::currentTemperature() const 0506 { 0507 return devices->cameraTemperature(); 0508 } 0509 0510 double SequenceJob::currentGain() const 0511 { 0512 return devices->cameraGain(); 0513 } 0514 0515 double SequenceJob::currentOffset() const 0516 { 0517 return devices->cameraOffset(); 0518 } 0519 0520 void SequenceJob::prepareCapture() 0521 { 0522 // simply forward it to the state machine 0523 switch (getFrameType()) 0524 { 0525 case FRAME_LIGHT: 0526 state->prepareLightFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(), 0527 jobType() == SequenceJob::JOBTYPE_PREVIEW); 0528 break; 0529 case FRAME_FLAT: 0530 state->prepareFlatFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(), 0531 jobType() == SequenceJob::JOBTYPE_PREVIEW); 0532 break; 0533 case FRAME_DARK: 0534 state->prepareDarkFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(), 0535 jobType() == SequenceJob::JOBTYPE_PREVIEW); 0536 break; 0537 case FRAME_BIAS: 0538 state->prepareBiasFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(), 0539 jobType() == SequenceJob::JOBTYPE_PREVIEW); 0540 break; 0541 default: 0542 // not refactored yet, immediately completed 0543 processPrepareComplete(); 0544 break; 0545 } 0546 } 0547 0548 void SequenceJob::processPrepareComplete(bool success) 0549 { 0550 emit prepareComplete(success); 0551 } 0552 0553 void SequenceJob::processAbortCapture() 0554 { 0555 disconnectDeviceAdaptor(); 0556 emit abortCapture(); 0557 } 0558 0559 IPState SequenceJob::checkFlatFramePendingTasksCompleted() 0560 { 0561 // no further checks necessary 0562 return IPS_OK; 0563 } 0564 0565 void SequenceJob::setCoreProperty(PropertyID id, const QVariant &value) 0566 { 0567 // Handle special cases 0568 switch (id) 0569 { 0570 case SJ_RemoteDirectory: 0571 { 0572 auto remoteDir = value.toString(); 0573 if (remoteDir.endsWith('/')) 0574 { 0575 remoteDir.chop(1); 0576 m_CoreProperties[id] = remoteDir; 0577 } 0578 } 0579 break; 0580 0581 default: 0582 break; 0583 } 0584 // store value 0585 m_CoreProperties[id] = value; 0586 } 0587 0588 QVariant SequenceJob::getCoreProperty(PropertyID id) const 0589 { 0590 return m_CoreProperties[id]; 0591 } 0592 0593 void SequenceJob::loadFrom(XMLEle *root, const QString &targetName, SequenceJobType jobType, 0594 QSharedPointer<CaptureModuleState> sharedState) 0595 { 0596 setJobType(jobType); 0597 0598 // Set default property values 0599 m_CoreProperties[SJ_Exposure] = -1; 0600 m_CoreProperties[SJ_Gain] = -1; 0601 m_CoreProperties[SJ_Offset] = -1; 0602 m_CoreProperties[SJ_ISOIndex] = -1; 0603 m_CoreProperties[SJ_Count] = -1; 0604 m_CoreProperties[SJ_Delay] = -1; 0605 m_CoreProperties[SJ_Binning] = QPoint(1, 1); 0606 m_CoreProperties[SJ_ROI] = QRect(0, 0, 0, 0); 0607 m_CoreProperties[SJ_EnforceTemperature] = false; 0608 m_CoreProperties[SJ_GuiderActive] = false; 0609 m_CoreProperties[SJ_DitherPerJobFrequency] = 0; 0610 m_CoreProperties[SJ_Encoding] = "FITS"; 0611 0612 // targetName overrides values from the XML document 0613 if (targetName != "") 0614 setCoreProperty(SequenceJob::SJ_TargetName, targetName); 0615 0616 if (root == nullptr) 0617 return; 0618 0619 bool isDarkFlat = false; 0620 0621 QLocale cLocale = QLocale::c(); 0622 XMLEle * ep; 0623 XMLEle * subEP; 0624 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) 0625 { 0626 if (!strcmp(tagXMLEle(ep), "Exposure")) 0627 setCoreProperty(SequenceJob::SJ_Exposure, cLocale.toDouble(pcdataXMLEle(ep))); 0628 else if (!strcmp(tagXMLEle(ep), "Format")) 0629 setCoreProperty(SequenceJob::SJ_Format, pcdataXMLEle(ep)); 0630 else if (!strcmp(tagXMLEle(ep), "Encoding")) 0631 { 0632 setCoreProperty(SequenceJob::SJ_Encoding, pcdataXMLEle(ep)); 0633 } 0634 else if (!strcmp(tagXMLEle(ep), "Binning")) 0635 { 0636 QPoint binning(1, 1); 0637 subEP = findXMLEle(ep, "X"); 0638 if (subEP) 0639 binning.setX(cLocale.toInt(pcdataXMLEle(subEP))); 0640 subEP = findXMLEle(ep, "Y"); 0641 if (subEP) 0642 binning.setY(cLocale.toInt(pcdataXMLEle(subEP))); 0643 0644 setCoreProperty(SequenceJob::SJ_Binning, binning); 0645 } 0646 else if (!strcmp(tagXMLEle(ep), "Frame")) 0647 { 0648 QRect roi(0, 0, 0, 0); 0649 subEP = findXMLEle(ep, "X"); 0650 if (subEP) 0651 roi.setX(cLocale.toInt(pcdataXMLEle(subEP))); 0652 subEP = findXMLEle(ep, "Y"); 0653 if (subEP) 0654 roi.setY(cLocale.toInt(pcdataXMLEle(subEP))); 0655 subEP = findXMLEle(ep, "W"); 0656 if (subEP) 0657 roi.setWidth(cLocale.toInt(pcdataXMLEle(subEP))); 0658 subEP = findXMLEle(ep, "H"); 0659 if (subEP) 0660 roi.setHeight(cLocale.toInt(pcdataXMLEle(subEP))); 0661 0662 setCoreProperty(SequenceJob::SJ_ROI, roi); 0663 } 0664 else if (!strcmp(tagXMLEle(ep), "Temperature")) 0665 { 0666 setTargetTemperature(cLocale.toDouble(pcdataXMLEle(ep))); 0667 0668 // If force attribute exist, we change cameraTemperatureS, otherwise do nothing. 0669 if (!strcmp(findXMLAttValu(ep, "force"), "true")) 0670 setCoreProperty(SequenceJob::SJ_EnforceTemperature, true); 0671 else if (!strcmp(findXMLAttValu(ep, "force"), "false")) 0672 setCoreProperty(SequenceJob::SJ_EnforceTemperature, false); 0673 } 0674 else if (!strcmp(tagXMLEle(ep), "Filter")) 0675 { 0676 const auto name = pcdataXMLEle(ep); 0677 const auto index = std::max(1, filterLabels().indexOf(name) + 1); 0678 setTargetFilter(index, name); 0679 } 0680 else if (!strcmp(tagXMLEle(ep), "Type")) 0681 { 0682 int index = frameTypes().indexOf(pcdataXMLEle(ep)); 0683 setFrameType(static_cast<CCDFrameType>(qMax(0, index))); 0684 } 0685 else if (!strcmp(tagXMLEle(ep), "TargetName")) 0686 { 0687 auto jobTarget = pcdataXMLEle(ep); 0688 0689 if (targetName == "") 0690 // use the target from the XML document 0691 setCoreProperty(SequenceJob::SJ_TargetName, jobTarget); 0692 else if (strcmp(jobTarget, "") != 0) 0693 // issue a warning that target from the XML document is ignored 0694 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job target name %1 ignored.").arg(jobTarget); 0695 } 0696 else if (!strcmp(tagXMLEle(ep), "Prefix")) 0697 { 0698 // RawPrefix is outdated and will be ignored 0699 subEP = findXMLEle(ep, "RawPrefix"); 0700 if (subEP) 0701 { 0702 auto jobTarget = pcdataXMLEle(subEP); 0703 0704 if (targetName == "") 0705 // use the target from the XML document 0706 setCoreProperty(SequenceJob::SJ_TargetName, jobTarget); 0707 else if (strcmp(jobTarget, "") != 0) 0708 // issue a warning that target from the XML document is ignored 0709 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job raw prefix %1 ignored.").arg(jobTarget); 0710 } 0711 bool filterEnabled = false, expEnabled = false, tsEnabled = false; 0712 subEP = findXMLEle(ep, "FilterEnabled"); 0713 if (subEP) 0714 filterEnabled = !strcmp("1", pcdataXMLEle(subEP)); 0715 subEP = findXMLEle(ep, "ExpEnabled"); 0716 if (subEP) 0717 expEnabled = !strcmp("1", pcdataXMLEle(subEP)); 0718 subEP = findXMLEle(ep, "TimeStampEnabled"); 0719 if (subEP) 0720 tsEnabled = !strcmp("1", pcdataXMLEle(subEP)); 0721 // build default format 0722 setCoreProperty(SequenceJob::SJ_PlaceholderFormat, 0723 PlaceholderPath::defaultFormat(filterEnabled, expEnabled, tsEnabled)); 0724 } 0725 else if (!strcmp(tagXMLEle(ep), "Count")) 0726 { 0727 setCoreProperty(SequenceJob::SJ_Count, cLocale.toInt(pcdataXMLEle(ep))); 0728 } 0729 else if (!strcmp(tagXMLEle(ep), "Delay")) 0730 { 0731 setCoreProperty(SequenceJob::SJ_Delay, cLocale.toInt(pcdataXMLEle(ep)) * 1000); 0732 } 0733 else if (!strcmp(tagXMLEle(ep), "PostCaptureScript")) 0734 { 0735 m_Scripts[SCRIPT_POST_CAPTURE] = pcdataXMLEle(ep); 0736 } 0737 else if (!strcmp(tagXMLEle(ep), "PreCaptureScript")) 0738 { 0739 m_Scripts[SCRIPT_PRE_CAPTURE] = pcdataXMLEle(ep); 0740 } 0741 else if (!strcmp(tagXMLEle(ep), "PostJobScript")) 0742 { 0743 m_Scripts[SCRIPT_POST_JOB] = pcdataXMLEle(ep); 0744 } 0745 else if (!strcmp(tagXMLEle(ep), "PreJobScript")) 0746 { 0747 m_Scripts[SCRIPT_PRE_JOB] = pcdataXMLEle(ep); 0748 } 0749 else if (!strcmp(tagXMLEle(ep), "GuideDitherPerJob")) 0750 { 0751 setCoreProperty(SequenceJob::SJ_DitherPerJobFrequency, cLocale.toInt(pcdataXMLEle(ep))); 0752 } 0753 else if (!strcmp(tagXMLEle(ep), "FITSDirectory")) 0754 { 0755 setCoreProperty(SequenceJob::SJ_LocalDirectory, pcdataXMLEle(ep)); 0756 } 0757 else if (!strcmp(tagXMLEle(ep), "PlaceholderFormat")) 0758 { 0759 setCoreProperty(SequenceJob::SJ_PlaceholderFormat, pcdataXMLEle(ep)); 0760 } 0761 else if (!strcmp(tagXMLEle(ep), "PlaceholderSuffix")) 0762 { 0763 setCoreProperty(SequenceJob::SJ_PlaceholderSuffix, cLocale.toUInt(pcdataXMLEle(ep))); 0764 } 0765 else if (!strcmp(tagXMLEle(ep), "RemoteDirectory")) 0766 { 0767 setCoreProperty(SequenceJob::SJ_RemoteDirectory, pcdataXMLEle(ep)); 0768 } 0769 else if (!strcmp(tagXMLEle(ep), "UploadMode")) 0770 { 0771 setUploadMode(static_cast<ISD::Camera::UploadMode>(cLocale.toInt(pcdataXMLEle(ep)))); 0772 } 0773 else if (!strcmp(tagXMLEle(ep), "ISOIndex")) 0774 { 0775 setISO(cLocale.toInt(pcdataXMLEle(ep))); 0776 } 0777 else if (!strcmp(tagXMLEle(ep), "Rotation")) 0778 { 0779 setTargetRotation(cLocale.toDouble(pcdataXMLEle(ep))); 0780 } 0781 else if (!strcmp(tagXMLEle(ep), "Properties")) 0782 { 0783 QMap<QString, QMap<QString, QVariant>> propertyMap; 0784 0785 for (subEP = nextXMLEle(ep, 1); subEP != nullptr; subEP = nextXMLEle(ep, 0)) 0786 { 0787 QMap<QString, QVariant> elements; 0788 XMLEle * oneElement = nullptr; 0789 for (oneElement = nextXMLEle(subEP, 1); oneElement != nullptr; oneElement = nextXMLEle(subEP, 0)) 0790 { 0791 const char * name = findXMLAttValu(oneElement, "name"); 0792 bool ok = false; 0793 // String 0794 auto xmlValue = pcdataXMLEle(oneElement); 0795 // Try to load it as double 0796 auto value = cLocale.toDouble(xmlValue, &ok); 0797 if (ok) 0798 elements[name] = value; 0799 else 0800 elements[name] = xmlValue; 0801 } 0802 0803 const char * name = findXMLAttValu(subEP, "name"); 0804 propertyMap[name] = elements; 0805 } 0806 0807 setCustomProperties(propertyMap); 0808 // read the gain and offset values from the custom properties 0809 setCoreProperty(SequenceJob::SJ_Gain, devices->cameraGain(propertyMap)); 0810 setCoreProperty(SequenceJob::SJ_Offset, devices->cameraOffset(propertyMap)); 0811 } 0812 else if (!strcmp(tagXMLEle(ep), "Calibration")) 0813 { 0814 // SQ_FORMAT_VERSION >= 2.7 0815 subEP = findXMLEle(ep, "PreAction"); 0816 if (subEP) 0817 { 0818 XMLEle * typeEP = findXMLEle(subEP, "Type"); 0819 if (typeEP) 0820 { 0821 setCalibrationPreAction(cLocale.toUInt(pcdataXMLEle(typeEP))); 0822 if (getCalibrationPreAction() & ACTION_WALL) 0823 { 0824 XMLEle * azEP = findXMLEle(subEP, "Az"); 0825 XMLEle * altEP = findXMLEle(subEP, "Alt"); 0826 0827 if (azEP && altEP) 0828 { 0829 setCalibrationPreAction((getCalibrationPreAction() & ~ACTION_PARK_MOUNT) | ACTION_WALL); 0830 SkyPoint wallCoord; 0831 wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP))); 0832 wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP))); 0833 setWallCoord(wallCoord); 0834 } 0835 else 0836 { 0837 qCWarning(KSTARS_EKOS_CAPTURE) << "Wall position coordinates missing, disabling slew to wall position action."; 0838 setCalibrationPreAction((getCalibrationPreAction() & ~ACTION_WALL) | ACTION_NONE); 0839 } 0840 } 0841 } 0842 } 0843 0844 // SQ_FORMAT_VERSION < 2.7 0845 subEP = findXMLEle(ep, "FlatSource"); 0846 if (subEP) 0847 { 0848 XMLEle * typeEP = findXMLEle(subEP, "Type"); 0849 if (typeEP) 0850 { 0851 // default 0852 setCalibrationPreAction(ACTION_NONE); 0853 if (!strcmp(pcdataXMLEle(typeEP), "Wall")) 0854 { 0855 XMLEle * azEP = findXMLEle(subEP, "Az"); 0856 XMLEle * altEP = findXMLEle(subEP, "Alt"); 0857 0858 if (azEP && altEP) 0859 { 0860 setCalibrationPreAction((getCalibrationPreAction() & ~ACTION_PARK_MOUNT) | ACTION_WALL); 0861 SkyPoint wallCoord; 0862 wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP))); 0863 wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP))); 0864 setWallCoord(wallCoord); 0865 } 0866 } 0867 } 0868 } 0869 0870 // SQ_FORMAT_VERSION < 2.7 0871 subEP = findXMLEle(ep, "PreMountPark"); 0872 if (subEP && !strcmp(pcdataXMLEle(subEP), "True")) 0873 setCalibrationPreAction(getCalibrationPreAction() | ACTION_PARK_MOUNT); 0874 0875 // SQ_FORMAT_VERSION < 2.7 0876 subEP = findXMLEle(ep, "PreDomePark"); 0877 if (subEP && !strcmp(pcdataXMLEle(subEP), "True")) 0878 setCalibrationPreAction(getCalibrationPreAction() | ACTION_PARK_DOME); 0879 0880 subEP = findXMLEle(ep, "FlatDuration"); 0881 if (subEP) 0882 { 0883 const char * dark = findXMLAttValu(subEP, "dark"); 0884 isDarkFlat = !strcmp(dark, "true"); 0885 0886 XMLEle * typeEP = findXMLEle(subEP, "Type"); 0887 if (typeEP) 0888 { 0889 if (!strcmp(pcdataXMLEle(typeEP), "Manual")) 0890 setFlatFieldDuration(DURATION_MANUAL); 0891 } 0892 0893 XMLEle * aduEP = findXMLEle(subEP, "Value"); 0894 if (aduEP) 0895 { 0896 setFlatFieldDuration(DURATION_ADU); 0897 setCoreProperty(SequenceJob::SJ_TargetADU, QVariant(cLocale.toDouble(pcdataXMLEle(aduEP)))); 0898 } 0899 0900 aduEP = findXMLEle(subEP, "Tolerance"); 0901 if (aduEP) 0902 { 0903 setCoreProperty(SequenceJob::SJ_TargetADUTolerance, QVariant(cLocale.toDouble(pcdataXMLEle(aduEP)))); 0904 } 0905 } 0906 } 0907 } 0908 if(isDarkFlat) 0909 setJobType(SequenceJob::JOBTYPE_DARKFLAT); 0910 } 0911 0912 void SequenceJob::saveTo(QTextStream &outstream, const QLocale &cLocale) const 0913 { 0914 auto roi = getCoreProperty(SequenceJob::SJ_ROI).toRect(); 0915 0916 outstream << "<Job>" << Qt::endl; 0917 0918 outstream << "<Exposure>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Exposure).toDouble()) << "</Exposure>" << 0919 Qt::endl; 0920 outstream << "<Format>" << getCoreProperty(SequenceJob::SJ_Format).toString() << "</Format>" << Qt::endl; 0921 outstream << "<Encoding>" << getCoreProperty(SequenceJob::SJ_Encoding).toString() << "</Encoding>" << Qt::endl; 0922 outstream << "<Binning>" << Qt::endl; 0923 outstream << "<X>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Binning).toPoint().x()) << "</X>" << Qt::endl; 0924 outstream << "<Y>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Binning).toPoint().y()) << "</Y>" << Qt::endl; 0925 outstream << "</Binning>" << Qt::endl; 0926 outstream << "<Frame>" << Qt::endl; 0927 outstream << "<X>" << cLocale.toString(roi.x()) << "</X>" << Qt::endl; 0928 outstream << "<Y>" << cLocale.toString(roi.y()) << "</Y>" << Qt::endl; 0929 outstream << "<W>" << cLocale.toString(roi.width()) << "</W>" << Qt::endl; 0930 outstream << "<H>" << cLocale.toString(roi.height()) << "</H>" << Qt::endl; 0931 outstream << "</Frame>" << Qt::endl; 0932 if (getTargetTemperature() != Ekos::INVALID_VALUE) 0933 outstream << "<Temperature force='" << (getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool() ? "true" : 0934 "false") << "'>" 0935 << cLocale.toString(getTargetTemperature()) << "</Temperature>" << Qt::endl; 0936 if (getTargetFilter() >= 0) 0937 outstream << "<Filter>" << getCoreProperty(SequenceJob::SJ_Filter).toString() << "</Filter>" << Qt::endl; 0938 outstream << "<Type>" << frameTypes()[getFrameType()] << "</Type>" << Qt::endl; 0939 outstream << "<Count>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Count).toInt()) << "</Count>" << Qt::endl; 0940 // ms to seconds 0941 outstream << "<Delay>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Delay).toInt() / 1000.0) << "</Delay>" << 0942 Qt::endl; 0943 if (getCoreProperty(SequenceJob::SJ_TargetName) != "") 0944 outstream << "<TargetName>" << getCoreProperty(SequenceJob::SJ_TargetName).toString() << "</TargetName>" << Qt::endl; 0945 if (getScript(SCRIPT_PRE_CAPTURE).isEmpty() == false) 0946 outstream << "<PreCaptureScript>" << getScript(SCRIPT_PRE_CAPTURE) << "</PreCaptureScript>" << Qt::endl; 0947 if (getScript(SCRIPT_POST_CAPTURE).isEmpty() == false) 0948 outstream << "<PostCaptureScript>" << getScript(SCRIPT_POST_CAPTURE) << "</PostCaptureScript>" << Qt::endl; 0949 if (getScript(SCRIPT_PRE_JOB).isEmpty() == false) 0950 outstream << "<PreJobScript>" << getScript(SCRIPT_PRE_JOB) << "</PreJobScript>" << Qt::endl; 0951 if (getScript(SCRIPT_POST_JOB).isEmpty() == false) 0952 outstream << "<PostJobScript>" << getScript(SCRIPT_POST_JOB) << "</PostJobScript>" << Qt::endl; 0953 outstream << "<GuideDitherPerJob>" 0954 << cLocale.toString(getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt()) << "</GuideDitherPerJob>" << 0955 Qt::endl; 0956 outstream << "<FITSDirectory>" << getCoreProperty(SequenceJob::SJ_LocalDirectory).toString() << "</FITSDirectory>" << 0957 Qt::endl; 0958 outstream << "<PlaceholderFormat>" << getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString() << 0959 "</PlaceholderFormat>" << 0960 Qt::endl; 0961 outstream << "<PlaceholderSuffix>" << getCoreProperty(SequenceJob::SJ_PlaceholderSuffix).toUInt() << 0962 "</PlaceholderSuffix>" << 0963 Qt::endl; 0964 outstream << "<UploadMode>" << getUploadMode() << "</UploadMode>" << Qt::endl; 0965 if (getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString().isEmpty() == false) 0966 outstream << "<RemoteDirectory>" << getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString() << "</RemoteDirectory>" 0967 << Qt::endl; 0968 if (getCoreProperty(SequenceJob::SJ_ISOIndex).toInt() != -1) 0969 outstream << "<ISOIndex>" << (getCoreProperty(SequenceJob::SJ_ISOIndex).toInt()) << "</ISOIndex>" << Qt::endl; 0970 if (getTargetRotation() != Ekos::INVALID_VALUE) 0971 outstream << "<Rotation>" << (getTargetRotation()) << "</Rotation>" << Qt::endl; 0972 QMapIterator<QString, QMap<QString, QVariant>> customIter(getCustomProperties()); 0973 outstream << "<Properties>" << Qt::endl; 0974 while (customIter.hasNext()) 0975 { 0976 customIter.next(); 0977 outstream << "<PropertyVector name='" << customIter.key() << "'>" << Qt::endl; 0978 QMap<QString, QVariant> elements = customIter.value(); 0979 QMapIterator<QString, QVariant> iter(elements); 0980 while (iter.hasNext()) 0981 { 0982 iter.next(); 0983 if (iter.value().type() == QVariant::String) 0984 { 0985 outstream << "<OneElement name='" << iter.key() 0986 << "'>" << iter.value().toString() << "</OneElement>" << Qt::endl; 0987 } 0988 else 0989 { 0990 outstream << "<OneElement name='" << iter.key() 0991 << "'>" << iter.value().toDouble() << "</OneElement>" << Qt::endl; 0992 } 0993 } 0994 outstream << "</PropertyVector>" << Qt::endl; 0995 } 0996 outstream << "</Properties>" << Qt::endl; 0997 0998 outstream << "<Calibration>" << Qt::endl; 0999 outstream << "<PreAction>" << Qt::endl; 1000 outstream << QString("<Type>%1</Type>").arg(getCalibrationPreAction()) << Qt::endl; 1001 if (getCalibrationPreAction() & ACTION_WALL) 1002 { 1003 outstream << "<Az>" << cLocale.toString(getWallCoord().az().Degrees()) << "</Az>" << Qt::endl; 1004 outstream << "<Alt>" << cLocale.toString(getWallCoord().alt().Degrees()) << "</Alt>" << Qt::endl; 1005 } 1006 outstream << "</PreAction>" << Qt::endl; 1007 1008 outstream << "<FlatDuration dark='" << (jobType() == SequenceJob::JOBTYPE_DARKFLAT ? "true" : "false") 1009 << "'>" << Qt::endl; 1010 if (getFlatFieldDuration() == DURATION_MANUAL) 1011 outstream << "<Type>Manual</Type>" << Qt::endl; 1012 else 1013 { 1014 outstream << "<Type>ADU</Type>" << Qt::endl; 1015 outstream << "<Value>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_TargetADU).toDouble()) << "</Value>" << 1016 Qt::endl; 1017 outstream << "<Tolerance>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_TargetADUTolerance).toDouble()) << 1018 "</Tolerance>" << Qt::endl; 1019 } 1020 outstream << "</FlatDuration>" << Qt::endl; 1021 outstream << "</Calibration>" << Qt::endl; 1022 outstream << "</Job>" << Qt::endl; 1023 } 1024 } 1025