Kstars

sequencejob.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "sequencejob.h"
8
9#include <knotification.h>
10#include <ekos_capture_debug.h>
11#include "capturedeviceadaptor.h"
12#include "skyobjects/skypoint.h"
13#include "ksnotification.h"
14
15#define MF_TIMER_TIMEOUT 90000
16#define MF_RA_DIFF_LIMIT 4
17
18namespace Ekos
19{
20QString const &SequenceJob::ISOMarker("_ISO8601");
21
22const QStringList SequenceJob::StatusStrings()
23{
24 static const QStringList names = {i18n("Idle"), i18n("In Progress"), i18n("Error"), i18n("Aborted"),
25 i18n("Complete")
26 };
27 return names;
28}
29
30
31/**
32 * @brief SequenceJob::SequenceJob Construct job from XML source
33 * @param root pointer to valid job stored in XML format.
34 */
35SequenceJob::SequenceJob(XMLEle * root, QString targetName)
36{
37 // set own unconnected state machine
38 QSharedPointer<CameraState> sharedState;
39 sharedState.reset(new CameraState);
40 state.reset(new SequenceJobState(sharedState));
41 // set simple device adaptor
42 devices.reset(new CaptureDeviceAdaptor());
43
44 init(SequenceJob::JOBTYPE_BATCH, root, sharedState, targetName);
45}
46
47SequenceJob::SequenceJob(const QSharedPointer<CaptureDeviceAdaptor> cp,
48 const QSharedPointer<CameraState> sharedState,
49 SequenceJobType jobType, XMLEle *root, QString targetName)
50{
51 devices = cp;
52 init(jobType, root, sharedState, targetName);
53}
54
55void Ekos::SequenceJob::init(SequenceJobType jobType, XMLEle *root,
56 QSharedPointer<Ekos::CameraState> sharedState,
57 const QString &targetName)
58{
59 // initialize the state machine
60 state.reset(new SequenceJobState(sharedState));
61
62 loadFrom(root, targetName, jobType);
63
64 // signal forwarding between this and the state machine
65 connect(state.data(), &SequenceJobState::prepareState, this, &SequenceJob::prepareState);
66 connect(state.data(), &SequenceJobState::prepareComplete, this, &SequenceJob::processPrepareComplete);
67 connect(state.data(), &SequenceJobState::abortCapture, this, &SequenceJob::processAbortCapture);
68 connect(state.data(), &SequenceJobState::newLog, this, &SequenceJob::newLog);
69 // start capturing as soon as the capture initialization is complete
70 connect(state.data(), &SequenceJobState::initCaptureComplete, this, &SequenceJob::capture);
71
72 // finish if XML document empty
73 if (root == nullptr)
74 return;
75
76 // create signature with current target
77 auto placeholderPath = Ekos::PlaceholderPath();
78 placeholderPath.processJobInfo(this);
79}
80
81void SequenceJob::resetStatus(JOBStatus status)
82{
83 setStatus(status);
84 setCalibrationStage(SequenceJobState::CAL_NONE);
85 switch (status)
86 {
87 case JOB_IDLE:
88 setCompleted(0);
89 // 2022.03.10: Keeps failing on Windows despite installing latest libindi
90#ifndef Q_OS_WIN
91 INDI_FALLTHROUGH;
92#endif
93 case JOB_ERROR:
94 case JOB_ABORTED:
95 case JOB_DONE:
96 m_ExposeLeft = 0;
97 m_CaptureRetires = 0;
98 m_JobProgressIgnored = false;
99 break;
100 case JOB_BUSY:
101 // do nothing
102 break;
103 }
104}
105
106void SequenceJob::abort()
107{
108 setStatus(JOB_ABORTED);
109 if (devices.data()->getActiveChip())
110 {
111 if (devices.data()->getActiveChip()->canAbort())
112 devices.data()->getActiveChip()->abortExposure();
113 devices.data()->getActiveChip()->setBatchMode(false);
114 }
115}
116
117void SequenceJob::done()
118{
119 setStatus(JOB_DONE);
120}
121
122int SequenceJob::getJobRemainingTime(double estimatedDownloadTime)
123{
124 double remaining = (getCoreProperty(SJ_Exposure).toDouble() +
125 estimatedDownloadTime +
126 getCoreProperty(SJ_Delay).toDouble() / 1000) *
127 (getCoreProperty(SJ_Count).toDouble() - getCompleted());
128
129 if (getStatus() == JOB_BUSY)
130 {
131 if (getExposeLeft() > 0.0)
132 remaining -= getCoreProperty(SJ_Exposure).toDouble() - getExposeLeft();
133 else
134 remaining += getExposeLeft() + estimatedDownloadTime;
135 }
136
137 return static_cast<int>(std::round(remaining));
138}
139
140void SequenceJob::setStatus(JOBStatus const in_status)
141{
142 state->reset(in_status);
143}
144
145void SequenceJob::setISO(int index)
146{
147 if (devices->getActiveChip())
148 {
149 setCoreProperty(SequenceJob::SJ_ISOIndex, index);
150 const auto isolist = devices->getActiveChip()->getISOList();
151 if (isolist.count() > index && index >= 0)
152 setCoreProperty(SequenceJob::SJ_ISO, isolist[index]);
153 }
154}
155
156const QVariant SequenceJob::getRemoteDirectory() const
157{
158 if (getCoreProperty(SJ_RemoteDirectory).toString().isEmpty())
159 return getCoreProperty(SJ_LocalDirectory);
160 else
161 return getCoreProperty(SJ_RemoteDirectory);
162}
163
164QStringList SequenceJob::frameTypes() const
165{
166 if (!devices->getActiveCamera())
167 return QStringList({"Light", "Bias", "Dark", "Flat"});
168
169 ISD::CameraChip *tChip = devices->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD);
170
171 return tChip->getFrameTypes();
172}
173
174QStringList SequenceJob::filterLabels() const
175{
176 if (devices->getFilterManager().isNull())
177 return QStringList();
178
179 return devices->getFilterManager()->getFilterLabels();
180
181}
182
183void SequenceJob::connectDeviceAdaptor()
184{
185 devices->setCurrentSequenceJobState(state);
186 // connect state machine with device adaptor
187 connect(state.data(), &SequenceJobState::readCurrentState, devices.data(),
188 &CaptureDeviceAdaptor::readCurrentState);
189 connect(state.data(), &SequenceJobState::flatSyncFocus, devices.data(),
190 &CaptureDeviceAdaptor::flatSyncFocus);
191 // connect device adaptor with state machine
192 connect(devices.data(), &CaptureDeviceAdaptor::flatSyncFocusChanged, state.data(),
193 &SequenceJobState::flatSyncFocusChanged);
194}
195
196void SequenceJob::disconnectDeviceAdaptor()
197{
198 devices->disconnectDevices(state.data());
199 disconnect(state.data(), &SequenceJobState::readCurrentState, devices.data(),
200 &CaptureDeviceAdaptor::readCurrentState);
201 disconnect(state.data(), &SequenceJobState::flatSyncFocus, devices.data(),
202 &CaptureDeviceAdaptor::flatSyncFocus);
203 disconnect(devices.data(), &CaptureDeviceAdaptor::flatSyncFocusChanged, state.data(),
204 &SequenceJobState::flatSyncFocusChanged);
205}
206
207void SequenceJob::startCapturing(bool autofocusReady, FITSMode mode)
208{
209 state->initCapture(getFrameType(), jobType() == SequenceJob::JOBTYPE_PREVIEW, autofocusReady, mode);
210}
211
212QString SequenceJob::setCameraDeviceProperties()
213{
214 // initialize the log entry
215 QString logentry = QString("Capture exposure = %1 sec, type = %2").arg(getCoreProperty(SJ_Exposure).toDouble()).arg(
216 CCDFrameTypeNames[getFrameType()]);
217 logentry.append(QString(", filter = %1, upload mode = %2").arg(getCoreProperty(SJ_Filter).toString()).arg(getUploadMode()));
218
219
220 QMapIterator<QString, QMap<QString, QVariant>> i(m_CustomProperties);
221 while (i.hasNext())
222 {
223 i.next();
224 auto customProp = devices.data()->getActiveCamera()->getProperty(i.key());
225 if (customProp)
226 {
227 QMap<QString, QVariant> elements = i.value();
228 QMapIterator<QString, QVariant> j(elements);
229
230 switch (customProp.getType())
231 {
232 case INDI_SWITCH:
233 {
234 auto sp = customProp.getSwitch();
235 while (j.hasNext())
236 {
237 j.next();
238 auto oneSwitch = sp->findWidgetByName(j.key().toLatin1().data());
239 if (oneSwitch)
240 oneSwitch->setState(static_cast<ISState>(j.value().toInt()));
241 }
242 devices.data()->getActiveCamera()->sendNewProperty(sp);
243 }
244 break;
245 case INDI_TEXT:
246 {
247 auto tp = customProp.getText();
248 while (j.hasNext())
249 {
250 j.next();
251 auto oneText = tp->findWidgetByName(j.key().toLatin1().data());
252 if (oneText)
253 oneText->setText(j.value().toString().toLatin1().constData());
254 }
255 devices.data()->getActiveCamera()->sendNewProperty(tp);
256 }
257 break;
258 case INDI_NUMBER:
259 {
260 auto np = customProp.getNumber();
261 while (j.hasNext())
262 {
263 j.next();
264 auto oneNumber = np->findWidgetByName(j.key().toLatin1().data());
265 if (oneNumber)
266 oneNumber->setValue(j.value().toDouble());
267 }
268 devices.data()->getActiveCamera()->sendNewProperty(np);
269 }
270 break;
271 default:
272 continue;
273 }
274 }
275 }
276
277
278 const int ISOIndex = getCoreProperty(SJ_ISOIndex).toInt();
279 if (ISOIndex != -1)
280 {
281 logentry.append(QString(", ISO index = %1").arg(ISOIndex));
282 if (ISOIndex != devices.data()->getActiveChip()->getISOIndex())
283 devices.data()->getActiveChip()->setISOIndex(ISOIndex);
284 }
285
286 const auto gain = getCoreProperty(SJ_Gain).toDouble();
287 if (gain >= 0)
288 {
289 logentry.append(QString(", gain = %1").arg(gain));
290 devices.data()->getActiveCamera()->setGain(gain);
291 }
292
293 const auto offset = getCoreProperty(SJ_Offset).toDouble();
294 if (offset >= 0)
295 {
296 logentry.append(QString(", offset = %1").arg(offset));
297 devices.data()->getActiveCamera()->setOffset(offset);
298 }
299
300 const auto remoteFormatDirectory = getCoreProperty(SJ_RemoteFormatDirectory).toString();
301 const auto remoteFormatFilename = getCoreProperty(SJ_RemoteFormatFilename).toString();
302
303 if (jobType() == SequenceJob::JOBTYPE_PREVIEW && !isVideo())
304 {
305 if (devices.data()->getActiveCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
306 devices.data()->getActiveCamera()->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
307 }
308 else
309 devices.data()->getActiveCamera()->setUploadMode(m_UploadMode);
310
311 if (devices.data()->getActiveChip()->isBatchMode() &&
312 remoteFormatDirectory.isEmpty() == false &&
313 remoteFormatFilename.isEmpty() == false)
314 {
315 if (isVideo())
316 devices.data()->getActiveCamera()->setSERNameDirectory(remoteFormatFilename, remoteFormatDirectory);
317 else
318 devices.data()->getActiveCamera()->updateUploadSettings(remoteFormatDirectory, remoteFormatFilename);
319
320 }
321
322 if (isVideo())
323 {
324 // video settings
325 devices.data()->getActiveCamera()->setUploadMode(m_UploadMode);
326
327 devices.data()->getActiveCamera()->setStreamRecording(getCoreProperty(SJ_Format).toString());
328 devices.data()->getActiveCamera()->setStreamEncoding(getCoreProperty(SJ_Encoding).toString());
329 }
330 else
331 {
332 devices.data()->getActiveChip()->setBatchMode(jobType() != SequenceJob::JOBTYPE_PREVIEW);
333 devices.data()->getActiveCamera()->setSeqPrefix(getCoreProperty(SJ_FullPrefix).toString());
334 logentry.append(QString(", batch mode = %1, seq prefix = %2").arg(jobType() != SequenceJob::JOBTYPE_PREVIEW ? "true" :
335 "false").arg(getCoreProperty(SJ_FullPrefix).toString()));
336
337
338 devices.data()->getActiveCamera()->setCaptureFormat(getCoreProperty(SJ_Format).toString());
339 devices.data()->getActiveCamera()->setEncodingFormat(getCoreProperty(SJ_Encoding).toString());
340 devices.data()->getActiveChip()->setFrameType(getFrameType());
341 }
342 if (getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
343 logentry.append(QString(", remote dir = %1, remote format = %2").arg(remoteFormatDirectory).arg(remoteFormatFilename));
344
345 logentry.append(QString(", format = %1, encoding = %2").arg(getCoreProperty(SJ_Format).toString()).arg(getCoreProperty(
346 SJ_Encoding).toString()));
347
348 // Only attempt to set ROI and Binning if CCD transfer format is FITS or XISF
349 int currentBinX = 1, currentBinY = 1;
350 devices.data()->getActiveChip()->getBinning(&currentBinX, &currentBinY);
351
352 const auto binning = getCoreProperty(SJ_Binning).toPoint();
353 // N.B. Always set binning _before_ setting frame because if the subframed image
354 // is problematic in 1x1 but works fine for 2x2, then it would fail it was set first
355 // So setting binning first always ensures this will work.
356 if (devices.data()->getActiveChip()->canBin())
357 {
358 if (devices.data()->getActiveChip()->setBinning(binning.x(), binning.y()) == false)
359 {
360 qCWarning(KSTARS_EKOS_CAPTURE()) << "Cannot set binning to " << "x =" << binning.x() << ", y =" << binning.y();
361 setStatus(JOB_ERROR);
362 emit captureStarted(CAPTURE_BIN_ERROR);
363 }
364 else
365 logentry.append(QString(", binning = %1x%2").arg(binning.x()).arg(binning.y()));
366 }
367 else
368 logentry.append(QString(", Cannot bin"));
369
370
371 const auto roi = getCoreProperty(SJ_ROI).toRect();
372
373 if (devices.data()->getActiveChip()->canSubframe())
374 {
375 if ((roi.width() > 0 && roi.height() > 0) && devices.data()->getActiveChip()->setFrame(roi.x(),
376 roi.y(),
377 roi.width(),
378 roi.height(),
379 currentBinX != binning.x()) == false)
380 {
381 qCWarning(KSTARS_EKOS_CAPTURE()) << "Cannot set ROI to " << "x =" << roi.x() << ", y =" << roi.y() << ", widht =" <<
382 roi.width() << "height =" << roi.height();
383 setStatus(JOB_ERROR);
384 emit captureStarted(CAPTURE_FRAME_ERROR);
385 }
386 else
387 logentry.append(QString(", ROI = (%1+%2, %3+%4)").arg(roi.x()).arg(roi.width()).arg(roi.y()).arg(roi.width()));
388 }
389 else
390 logentry.append(", Cannot subframe");
391
392 return logentry;
393}
394
395void SequenceJob::capture(FITSMode mode)
396{
397 if (!devices.data()->getActiveCamera() || !devices.data()->getActiveChip())
398 return;
399
400 QString logentry = setCameraDeviceProperties();
401
402 // update the status
403 setStatus(getStatus());
404
405 if (isVideo())
406 {
407 devices.data()->getActiveCamera()->setStreamExposure(getCoreProperty(SJ_Exposure).toDouble());
408 auto frames = getCoreProperty(SJ_Count).toUInt();
409 auto success = devices.data()->getActiveCamera()->startFramesRecording(frames);
410 if (! success)
411 {
412 qCWarning(KSTARS_EKOS_CAPTURE) << "Start recording failed!";
413 emit captureStarted(CAPTURE_FRAME_ERROR);
414 return;
415 }
416
417 }
418 else
419 {
420 // In case FITS Viewer is not enabled. Then for flat frames, we still need to keep the data
421 // otherwise INDI CCD would simply discard loading the data in batch mode as the data are already
422 // saved to disk and since no extra processing is required, FITSData is not loaded up with the data.
423 // But in case of automatically calculated flat frames, we need FITSData.
424 // Therefore, we need to explicitly set mode to FITS_CALIBRATE so that FITSData is generated.
425 devices.data()->getActiveChip()->setCaptureMode(mode);
426 devices.data()->getActiveChip()->setCaptureFilter(FITS_NONE);
427
428 m_ExposeLeft = getCoreProperty(SJ_Exposure).toDouble();
429 devices.data()->getActiveChip()->capture(m_ExposeLeft);
430 }
431
432 emit captureStarted(CAPTURE_OK);
433
434 // create log entry with settings
435 qCInfo(KSTARS_EKOS_CAPTURE) << logentry;
436}
437
438void SequenceJob::setTargetFilter(int pos, const QString &name)
439{
440 state->targetFilterID = pos;
441 setCoreProperty(SJ_Filter, name);
442}
443
444double SequenceJob::getExposeLeft() const
445{
446 return m_ExposeLeft;
447}
448
449void SequenceJob::setExposeLeft(double value)
450{
451 m_ExposeLeft = value;
452}
453
454
455int SequenceJob::getCaptureRetires() const
456{
457 return m_CaptureRetires;
458}
459
460void SequenceJob::setCaptureRetires(int value)
461{
462 m_CaptureRetires = value;
463}
464
465int SequenceJob::getCurrentFilter() const
466{
467 return state->m_CameraState->currentFilterID;
468}
469
470ISD::Mount::PierSide SequenceJob::getPierSide() const
471{
472 return state->m_CameraState->getPierSide();
473}
474
475// Setter: Set upload mode
476void SequenceJob::setUploadMode(ISD::Camera::UploadMode value)
477{
478 m_UploadMode = value;
479}
480// Getter: get upload mode
481ISD::Camera::UploadMode SequenceJob::getUploadMode() const
482{
483 return m_UploadMode;
484}
485
486// Setter: Set flat field source
487void SequenceJob::setCalibrationPreAction(uint32_t value)
488{
489 state->m_CalibrationPreAction = value;
490}
491// Getter: Get calibration pre action
492uint32_t SequenceJob::getCalibrationPreAction() const
493{
494 return state->m_CalibrationPreAction;
495}
496
497void SequenceJob::setWallCoord(const SkyPoint &value)
498{
499 state->wallCoord = value;
500}
501
502const SkyPoint &SequenceJob::getWallCoord() const
503{
504 return state->wallCoord;
505}
506
507// Setter: Set flat field duration
508void SequenceJob::setFlatFieldDuration(FlatFieldDuration value)
509{
510 m_FlatFieldDuration = value;
511}
512
513// Getter: Get flat field duration
514FlatFieldDuration SequenceJob::getFlatFieldDuration() const
515{
516 return m_FlatFieldDuration;
517}
518
519void SequenceJob::setJobProgressIgnored(bool value)
520{
521 m_JobProgressIgnored = value;
522}
523
524bool SequenceJob::getJobProgressIgnored() const
525{
526 return m_JobProgressIgnored;
527}
528
529void SequenceJob::updateDeviceStates()
530{
531 setLightBox(devices->lightBox());
532 addMount(devices->mount());
533 setDome(devices->dome());
534 setDustCap(devices->dustCap());
535}
536
537void SequenceJob::setLightBox(ISD::LightBox * lightBox)
538{
539 state->m_CameraState->hasLightBox = (lightBox != nullptr);
540}
541
542void SequenceJob::setDustCap(ISD::DustCap * dustCap)
543{
544 state->m_CameraState->hasDustCap = (dustCap != nullptr);
545}
546
547void SequenceJob::addMount(ISD::Mount * scope)
548{
549 state->m_CameraState->hasTelescope = (scope != nullptr);
550}
551
552void SequenceJob::setDome(ISD::Dome * dome)
553{
554 state->m_CameraState->hasDome = (dome != nullptr);
555}
556
557double SequenceJob::currentTemperature() const
558{
559 return devices->cameraTemperature();
560}
561
562double SequenceJob::currentGain() const
563{
564 return devices->cameraGain();
565}
566
567double SequenceJob::currentOffset() const
568{
569 return devices->cameraOffset();
570}
571
572void SequenceJob::prepareCapture()
573{
574 // simply forward it to the state machine
575 switch (getFrameType())
576 {
577 case FRAME_LIGHT:
578 case FRAME_VIDEO:
579 state->prepareLightFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
580 jobType() == SequenceJob::JOBTYPE_PREVIEW);
581 break;
582 case FRAME_FLAT:
583 state->prepareFlatFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
584 jobType() == SequenceJob::JOBTYPE_PREVIEW);
585 break;
586 case FRAME_DARK:
587 state->prepareDarkFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
588 jobType() == SequenceJob::JOBTYPE_PREVIEW);
589 break;
590 case FRAME_BIAS:
591 state->prepareBiasFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
592 jobType() == SequenceJob::JOBTYPE_PREVIEW);
593 break;
594 default:
595 // not refactored yet, immediately completed
596 processPrepareComplete();
597 break;
598 }
599}
600
601void SequenceJob::processPrepareComplete(bool success)
602{
603 qDebug(KSTARS_EKOS_CAPTURE) << "Sequence job: capture preparation" << (success ? "succeeded" : "failed");
604 emit prepareComplete(success);
605}
606
607void SequenceJob::processAbortCapture()
608{
609 disconnectDeviceAdaptor();
610 emit abortCapture();
611}
612
613IPState SequenceJob::checkFlatFramePendingTasksCompleted()
614{
615 // no further checks necessary
616 return IPS_OK;
617}
618
619void SequenceJob::setCoreProperty(PropertyID id, const QVariant &value)
620{
621 // Handle special cases
622 switch (id)
623 {
624 case SJ_RemoteDirectory:
625 {
626 auto remoteDir = value.toString();
627 if (remoteDir.endsWith('/'))
628 {
629 remoteDir.chop(1);
630 m_CoreProperties[id] = remoteDir;
631 }
632 }
633 break;
634
635 default:
636 break;
637 }
638 // store value
639 m_CoreProperties[id] = value;
640}
641
642QVariant SequenceJob::getCoreProperty(PropertyID id) const
643{
644 return m_CoreProperties[id];
645}
646
647void SequenceJob::loadFrom(XMLEle *root, const QString &targetName, SequenceJobType jobType)
648{
649 setJobType(jobType);
650
651 // Set default property values
652 m_CoreProperties[SJ_Exposure] = -1;
653 m_CoreProperties[SJ_Gain] = -1;
654 m_CoreProperties[SJ_Offset] = -1;
655 m_CoreProperties[SJ_ISOIndex] = -1;
656 m_CoreProperties[SJ_Count] = -1;
657 m_CoreProperties[SJ_Delay] = -1;
658 m_CoreProperties[SJ_Binning] = QPoint(1, 1);
659 m_CoreProperties[SJ_ROI] = QRect(0, 0, 0, 0);
660 m_CoreProperties[SJ_EnforceTemperature] = false;
661 m_CoreProperties[SJ_GuiderActive] = false;
662 m_CoreProperties[SJ_DitherPerJobEnabled] = true;
663 m_CoreProperties[SJ_DitherPerJobFrequency] = 0;
664 m_CoreProperties[SJ_Encoding] = "FITS";
665
666 // targetName overrides values from the XML document
667 if (targetName != "")
668 setCoreProperty(SequenceJob::SJ_TargetName, targetName);
669
670 if (root == nullptr)
671 return;
672
673 bool isDarkFlat = false;
674
675 QLocale cLocale = QLocale::c();
676 XMLEle * ep;
677 XMLEle * subEP;
678 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
679 {
680 if (!strcmp(tagXMLEle(ep), "Exposure"))
681 setCoreProperty(SequenceJob::SJ_Exposure, cLocale.toDouble(pcdataXMLEle(ep)));
682 else if (!strcmp(tagXMLEle(ep), "Format"))
683 setCoreProperty(SequenceJob::SJ_Format, QString(pcdataXMLEle(ep)));
684 else if (!strcmp(tagXMLEle(ep), "Encoding"))
685 {
686 setCoreProperty(SequenceJob::SJ_Encoding, QString(pcdataXMLEle(ep)));
687 }
688 else if (!strcmp(tagXMLEle(ep), "Binning"))
689 {
690 QPoint binning(1, 1);
691 subEP = findXMLEle(ep, "X");
692 if (subEP)
693 binning.setX(cLocale.toInt(pcdataXMLEle(subEP)));
694 subEP = findXMLEle(ep, "Y");
695 if (subEP)
696 binning.setY(cLocale.toInt(pcdataXMLEle(subEP)));
697
698 setCoreProperty(SequenceJob::SJ_Binning, binning);
699 }
700 else if (!strcmp(tagXMLEle(ep), "Frame"))
701 {
702 QRect roi(0, 0, 0, 0);
703 subEP = findXMLEle(ep, "X");
704 if (subEP)
705 roi.setX(cLocale.toInt(pcdataXMLEle(subEP)));
706 subEP = findXMLEle(ep, "Y");
707 if (subEP)
708 roi.setY(cLocale.toInt(pcdataXMLEle(subEP)));
709 subEP = findXMLEle(ep, "W");
710 if (subEP)
711 roi.setWidth(cLocale.toInt(pcdataXMLEle(subEP)));
712 subEP = findXMLEle(ep, "H");
713 if (subEP)
714 roi.setHeight(cLocale.toInt(pcdataXMLEle(subEP)));
715
716 setCoreProperty(SequenceJob::SJ_ROI, roi);
717 }
718 else if (!strcmp(tagXMLEle(ep), "Temperature"))
719 {
720 setTargetTemperature(cLocale.toDouble(pcdataXMLEle(ep)));
721
722 // If force attribute exist, we change cameraTemperatureS, otherwise do nothing.
723 if (!strcmp(findXMLAttValu(ep, "force"), "true"))
724 setCoreProperty(SequenceJob::SJ_EnforceTemperature, true);
725 else if (!strcmp(findXMLAttValu(ep, "force"), "false"))
726 setCoreProperty(SequenceJob::SJ_EnforceTemperature, false);
727 }
728 else if (!strcmp(tagXMLEle(ep), "Filter"))
729 {
730 const auto name = pcdataXMLEle(ep);
731 const auto index = std::max(1, (int)(filterLabels().indexOf(name) + 1));
732 setTargetFilter(index, name);
733 }
734 else if (!strcmp(tagXMLEle(ep), "Type"))
735 {
736 int index = frameTypes().indexOf(pcdataXMLEle(ep));
737 setFrameType(static_cast<CCDFrameType>(qMax(0, index)));
738 }
739 else if (!strcmp(tagXMLEle(ep), "TargetName"))
740 {
741 QString jobTarget = pcdataXMLEle(ep);
742
743 if (targetName.isEmpty())
744 // use the target from the XML document
745 setCoreProperty(SequenceJob::SJ_TargetName, QString(jobTarget));
746 else if (!jobTarget.isEmpty())
747 // issue a warning that target from the XML document is ignored
748 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job target name %1 ignored in favor of %2.").arg(jobTarget, targetName);
749 }
750 else if (!strcmp(tagXMLEle(ep), "Prefix"))
751 {
752 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job is using outdated format. Please create a new sequence file");
753 // RawPrefix is outdated and will be ignored
754 subEP = findXMLEle(ep, "RawPrefix");
755 if (subEP)
756 {
757 QString jobTarget = pcdataXMLEle(subEP);
758
759 if (targetName.isEmpty())
760 // use the target from the XML document
761 setCoreProperty(SequenceJob::SJ_TargetName, QString(jobTarget));
762 else if (!jobTarget.isEmpty())
763 // issue a warning that target from the XML document is ignored
764 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job target name %1 ignored in favor of %2.").arg(jobTarget, targetName);
765 }
766 bool filterEnabled = false, expEnabled = false, tsEnabled = false;
767 subEP = findXMLEle(ep, "FilterEnabled");
768 if (subEP)
769 filterEnabled = !strcmp("1", pcdataXMLEle(subEP));
770 subEP = findXMLEle(ep, "ExpEnabled");
771 if (subEP)
772 expEnabled = !strcmp("1", pcdataXMLEle(subEP));
773 subEP = findXMLEle(ep, "TimeStampEnabled");
774 if (subEP)
775 tsEnabled = !strcmp("1", pcdataXMLEle(subEP));
776 // build default format
777 setCoreProperty(SequenceJob::SJ_PlaceholderFormat,
778 PlaceholderPath::defaultFormat(filterEnabled, expEnabled, tsEnabled));
779 }
780 else if (!strcmp(tagXMLEle(ep), "Count"))
781 {
782 setCoreProperty(SequenceJob::SJ_Count, cLocale.toInt(pcdataXMLEle(ep)));
783 }
784 else if (!strcmp(tagXMLEle(ep), "Delay"))
785 {
786 setCoreProperty(SequenceJob::SJ_Delay, cLocale.toInt(pcdataXMLEle(ep)) * 1000);
787 }
788 else if (!strcmp(tagXMLEle(ep), "PostCaptureScript"))
789 {
790 m_Scripts[SCRIPT_POST_CAPTURE] = pcdataXMLEle(ep);
791 }
792 else if (!strcmp(tagXMLEle(ep), "PreCaptureScript"))
793 {
794 m_Scripts[SCRIPT_PRE_CAPTURE] = pcdataXMLEle(ep);
795 }
796 else if (!strcmp(tagXMLEle(ep), "PostJobScript"))
797 {
798 m_Scripts[SCRIPT_POST_JOB] = pcdataXMLEle(ep);
799 }
800 else if (!strcmp(tagXMLEle(ep), "PreJobScript"))
801 {
802 m_Scripts[SCRIPT_PRE_JOB] = pcdataXMLEle(ep);
803 }
804 else if (!strcmp(tagXMLEle(ep), "GuideDitherPerJob"))
805 {
806 const int value = cLocale.toInt(pcdataXMLEle(ep));
807 if (value >= 0)
808 {
809 setCoreProperty(SequenceJob::SJ_DitherPerJobFrequency, value);
810 setCoreProperty(SequenceJob::SJ_DitherPerJobEnabled, true);
811 }
812 else
813 setCoreProperty(SequenceJob::SJ_DitherPerJobEnabled, false);
814 }
815 else if (!strcmp(tagXMLEle(ep), "FITSDirectory"))
816 {
817 setCoreProperty(SequenceJob::SJ_LocalDirectory, QString(pcdataXMLEle(ep)));
818 }
819 else if (!strcmp(tagXMLEle(ep), "PlaceholderFormat"))
820 {
821 setCoreProperty(SequenceJob::SJ_PlaceholderFormat, QString(pcdataXMLEle(ep)));
822 }
823 else if (!strcmp(tagXMLEle(ep), "PlaceholderSuffix"))
824 {
825 setCoreProperty(SequenceJob::SJ_PlaceholderSuffix, cLocale.toUInt(pcdataXMLEle(ep)));
826 }
827 else if (!strcmp(tagXMLEle(ep), "RemoteDirectory"))
828 {
829 setCoreProperty(SequenceJob::SJ_RemoteDirectory, QString(pcdataXMLEle(ep)));
830 }
831 else if (!strcmp(tagXMLEle(ep), "UploadMode"))
832 {
833 setUploadMode(static_cast<ISD::Camera::UploadMode>(cLocale.toInt(pcdataXMLEle(ep))));
834 }
835 else if (!strcmp(tagXMLEle(ep), "ISOIndex"))
836 {
837 setISO(cLocale.toInt(pcdataXMLEle(ep)));
838 }
839 else if (!strcmp(tagXMLEle(ep), "Rotation"))
840 {
841 setTargetRotation(cLocale.toDouble(pcdataXMLEle(ep)));
842 }
843 else if (!strcmp(tagXMLEle(ep), "Properties"))
844 {
845 QMap<QString, QMap<QString, QVariant>> propertyMap;
846
847 for (subEP = nextXMLEle(ep, 1); subEP != nullptr; subEP = nextXMLEle(ep, 0))
848 {
849 QMap<QString, QVariant> elements;
850 XMLEle * oneElement = nullptr;
851 for (oneElement = nextXMLEle(subEP, 1); oneElement != nullptr; oneElement = nextXMLEle(subEP, 0))
852 {
853 const char * name = findXMLAttValu(oneElement, "name");
854 bool ok = false;
855 // String
856 auto xmlValue = pcdataXMLEle(oneElement);
857 // Try to load it as double
858 auto value = cLocale.toDouble(xmlValue, &ok);
859 if (ok)
860 elements[name] = value;
861 else
862 elements[name] = *xmlValue;
863 }
864
865 const char * name = findXMLAttValu(subEP, "name");
866 propertyMap[name] = elements;
867 }
868
869 setCustomProperties(propertyMap);
870 // read the gain and offset values from the custom properties
871 setCoreProperty(SequenceJob::SJ_Gain, devices->cameraGain(propertyMap));
872 setCoreProperty(SequenceJob::SJ_Offset, devices->cameraOffset(propertyMap));
873 }
874 else if (!strcmp(tagXMLEle(ep), "Calibration"))
875 {
876 // SQ_FORMAT_VERSION >= 2.7
877 subEP = findXMLEle(ep, "PreAction");
878 if (subEP)
879 {
880 XMLEle * typeEP = findXMLEle(subEP, "Type");
881 if (typeEP)
882 {
883 setCalibrationPreAction(cLocale.toUInt(pcdataXMLEle(typeEP)));
884 if (getCalibrationPreAction() & CAPTURE_PREACTION_WALL)
885 {
886 XMLEle * azEP = findXMLEle(subEP, "Az");
887 XMLEle * altEP = findXMLEle(subEP, "Alt");
888
889 if (azEP && altEP)
890 {
891 setCalibrationPreAction((getCalibrationPreAction() & ~CAPTURE_PREACTION_PARK_MOUNT) | CAPTURE_PREACTION_WALL);
892 SkyPoint wallCoord;
893 wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP)));
894 wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP)));
895 setWallCoord(wallCoord);
896 }
897 else
898 {
899 qCWarning(KSTARS_EKOS_CAPTURE) << "Wall position coordinates missing, disabling slew to wall position action.";
900 setCalibrationPreAction((getCalibrationPreAction() & ~CAPTURE_PREACTION_WALL) | CAPTURE_PREACTION_NONE);
901 }
902 }
903 }
904 }
905
906 // SQ_FORMAT_VERSION < 2.7
907 subEP = findXMLEle(ep, "FlatSource");
908 if (subEP)
909 {
910 XMLEle * typeEP = findXMLEle(subEP, "Type");
911 if (typeEP)
912 {
913 // default
914 setCalibrationPreAction(CAPTURE_PREACTION_NONE);
915 if (!strcmp(pcdataXMLEle(typeEP), "Wall"))
916 {
917 XMLEle * azEP = findXMLEle(subEP, "Az");
918 XMLEle * altEP = findXMLEle(subEP, "Alt");
919
920 if (azEP && altEP)
921 {
922 setCalibrationPreAction((getCalibrationPreAction() & ~CAPTURE_PREACTION_PARK_MOUNT) | CAPTURE_PREACTION_WALL);
923 SkyPoint wallCoord;
924 wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP)));
925 wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP)));
926 setWallCoord(wallCoord);
927 }
928 }
929 }
930 }
931
932 // SQ_FORMAT_VERSION < 2.7
933 subEP = findXMLEle(ep, "PreMountPark");
934 if (subEP && !strcmp(pcdataXMLEle(subEP), "True"))
935 setCalibrationPreAction(getCalibrationPreAction() | CAPTURE_PREACTION_PARK_MOUNT);
936
937 // SQ_FORMAT_VERSION < 2.7
938 subEP = findXMLEle(ep, "PreDomePark");
939 if (subEP && !strcmp(pcdataXMLEle(subEP), "True"))
940 setCalibrationPreAction(getCalibrationPreAction() | CAPTURE_PREACTION_PARK_DOME);
941
942 subEP = findXMLEle(ep, "FlatDuration");
943 if (subEP)
944 {
945 const char * dark = findXMLAttValu(subEP, "dark");
946 isDarkFlat = !strcmp(dark, "true");
947
948 XMLEle * typeEP = findXMLEle(subEP, "Type");
949 if (typeEP)
950 {
951 if (!strcmp(pcdataXMLEle(typeEP), "Manual"))
952 setFlatFieldDuration(DURATION_MANUAL);
953 }
954
955 XMLEle * aduEP = findXMLEle(subEP, "Value");
956 if (aduEP)
957 {
958 setFlatFieldDuration(DURATION_ADU);
959 setCoreProperty(SequenceJob::SJ_TargetADU, QVariant(cLocale.toDouble(pcdataXMLEle(aduEP))));
960 }
961
962 aduEP = findXMLEle(subEP, "Tolerance");
963 if (aduEP)
964 {
965 setCoreProperty(SequenceJob::SJ_TargetADUTolerance, QVariant(cLocale.toDouble(pcdataXMLEle(aduEP))));
966 }
967 aduEP = findXMLEle(subEP, "SkyFlat");
968 if (aduEP)
969 {
970 setCoreProperty(SequenceJob::SJ_SkyFlat, (bool)!strcmp(pcdataXMLEle(aduEP), "true"));
971 }
972 }
973 }
974 }
975 if(isDarkFlat)
976 setJobType(SequenceJob::JOBTYPE_DARKFLAT);
977}
978
979void SequenceJob::saveTo(QTextStream &outstream, const QLocale &cLocale) const
980{
981 auto roi = getCoreProperty(SequenceJob::SJ_ROI).toRect();
982 auto ditherPerJobEnabled = getCoreProperty(SequenceJob::SJ_DitherPerJobEnabled).toBool();
983 auto ditherPerJobFrequency = getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt();
984
985 outstream << "<Job>" << Qt::endl;
986
987 outstream << "<Exposure>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Exposure).toDouble()) << "</Exposure>" <<
988 Qt::endl;
989 outstream << "<Format>" << getCoreProperty(SequenceJob::SJ_Format).toString() << "</Format>" << Qt::endl;
990 outstream << "<Encoding>" << getCoreProperty(SequenceJob::SJ_Encoding).toString() << "</Encoding>" << Qt::endl;
991 outstream << "<Binning>" << Qt::endl;
992 outstream << "<X>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Binning).toPoint().x()) << "</X>" << Qt::endl;
993 outstream << "<Y>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Binning).toPoint().y()) << "</Y>" << Qt::endl;
994 outstream << "</Binning>" << Qt::endl;
995 outstream << "<Frame>" << Qt::endl;
996 outstream << "<X>" << cLocale.toString(roi.x()) << "</X>" << Qt::endl;
997 outstream << "<Y>" << cLocale.toString(roi.y()) << "</Y>" << Qt::endl;
998 outstream << "<W>" << cLocale.toString(roi.width()) << "</W>" << Qt::endl;
999 outstream << "<H>" << cLocale.toString(roi.height()) << "</H>" << Qt::endl;
1000 outstream << "</Frame>" << Qt::endl;
1001 if (getTargetTemperature() != Ekos::INVALID_VALUE)
1002 outstream << "<Temperature force='" << (getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool() ? "true" :
1003 "false") << "'>"
1004 << cLocale.toString(getTargetTemperature()) << "</Temperature>" << Qt::endl;
1005 if (getTargetFilter() >= 0)
1006 outstream << "<Filter>" << getCoreProperty(SequenceJob::SJ_Filter).toString() << "</Filter>" << Qt::endl;
1007 outstream << "<Type>" << frameTypes()[getFrameType()] << "</Type>" << Qt::endl;
1008 outstream << "<Count>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Count).toInt()) << "</Count>" << Qt::endl;
1009 // ms to seconds
1010 outstream << "<Delay>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Delay).toInt() / 1000.0) << "</Delay>" <<
1011 Qt::endl;
1012 if (getCoreProperty(SequenceJob::SJ_TargetName) != "")
1013 outstream << "<TargetName>" << getCoreProperty(SequenceJob::SJ_TargetName).toString() << "</TargetName>" << Qt::endl;
1014 if (getScript(SCRIPT_PRE_CAPTURE).isEmpty() == false)
1015 outstream << "<PreCaptureScript>" << getScript(SCRIPT_PRE_CAPTURE) << "</PreCaptureScript>" << Qt::endl;
1016 if (getScript(SCRIPT_POST_CAPTURE).isEmpty() == false)
1017 outstream << "<PostCaptureScript>" << getScript(SCRIPT_POST_CAPTURE) << "</PostCaptureScript>" << Qt::endl;
1018 if (getScript(SCRIPT_PRE_JOB).isEmpty() == false)
1019 outstream << "<PreJobScript>" << getScript(SCRIPT_PRE_JOB) << "</PreJobScript>" << Qt::endl;
1020 if (getScript(SCRIPT_POST_JOB).isEmpty() == false)
1021 outstream << "<PostJobScript>" << getScript(SCRIPT_POST_JOB) << "</PostJobScript>" << Qt::endl;
1022 outstream << "<GuideDitherPerJob>"
1023 << cLocale.toString(ditherPerJobEnabled ? ditherPerJobFrequency : -1) << "</GuideDitherPerJob>" <<
1024 Qt::endl;
1025 outstream << "<FITSDirectory>" << getCoreProperty(SequenceJob::SJ_LocalDirectory).toString() << "</FITSDirectory>" <<
1026 Qt::endl;
1027 outstream << "<PlaceholderFormat>" << getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString() <<
1028 "</PlaceholderFormat>" <<
1029 Qt::endl;
1030 outstream << "<PlaceholderSuffix>" << getCoreProperty(SequenceJob::SJ_PlaceholderSuffix).toUInt() <<
1031 "</PlaceholderSuffix>" <<
1032 Qt::endl;
1033 outstream << "<UploadMode>" << getUploadMode() << "</UploadMode>" << Qt::endl;
1034 if (getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString().isEmpty() == false)
1035 outstream << "<RemoteDirectory>" << getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString() << "</RemoteDirectory>"
1036 << Qt::endl;
1037 if (getCoreProperty(SequenceJob::SJ_ISOIndex).toInt() != -1)
1038 outstream << "<ISOIndex>" << (getCoreProperty(SequenceJob::SJ_ISOIndex).toInt()) << "</ISOIndex>" << Qt::endl;
1039 if (getTargetRotation() != Ekos::INVALID_VALUE)
1040 outstream << "<Rotation>" << (getTargetRotation()) << "</Rotation>" << Qt::endl;
1041 QMapIterator<QString, QMap<QString, QVariant>> customIter(getCustomProperties());
1042 outstream << "<Properties>" << Qt::endl;
1043 while (customIter.hasNext())
1044 {
1045 customIter.next();
1046 outstream << "<PropertyVector name='" << customIter.key() << "'>" << Qt::endl;
1047 QMap<QString, QVariant> elements = customIter.value();
1048 QMapIterator<QString, QVariant> iter(elements);
1049 while (iter.hasNext())
1050 {
1051 iter.next();
1052 if (iter.value().type() == QVariant::String)
1053 {
1054 outstream << "<OneElement name='" << iter.key()
1055 << "'>" << iter.value().toString() << "</OneElement>" << Qt::endl;
1056 }
1057 else
1058 {
1059 outstream << "<OneElement name='" << iter.key()
1060 << "'>" << iter.value().toDouble() << "</OneElement>" << Qt::endl;
1061 }
1062 }
1063 outstream << "</PropertyVector>" << Qt::endl;
1064 }
1065 outstream << "</Properties>" << Qt::endl;
1066
1067 outstream << "<Calibration>" << Qt::endl;
1068 outstream << "<PreAction>" << Qt::endl;
1069 outstream << QString("<Type>%1</Type>").arg(getCalibrationPreAction()) << Qt::endl;
1070 if (getCalibrationPreAction() & CAPTURE_PREACTION_WALL)
1071 {
1072 outstream << "<Az>" << cLocale.toString(getWallCoord().az().Degrees()) << "</Az>" << Qt::endl;
1073 outstream << "<Alt>" << cLocale.toString(getWallCoord().alt().Degrees()) << "</Alt>" << Qt::endl;
1074 }
1075 outstream << "</PreAction>" << Qt::endl;
1076
1077 outstream << "<FlatDuration dark='" << (jobType() == SequenceJob::JOBTYPE_DARKFLAT ? "true" : "false")
1078 << "'>" << Qt::endl;
1079 if (getFlatFieldDuration() == DURATION_MANUAL)
1080 outstream << "<Type>Manual</Type>" << Qt::endl;
1081 else
1082 {
1083 outstream << "<Type>ADU</Type>" << Qt::endl;
1084 outstream << "<Value>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_TargetADU).toDouble()) << "</Value>" <<
1085 Qt::endl;
1086 outstream << "<Tolerance>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_TargetADUTolerance).toDouble()) <<
1087 "</Tolerance>" << Qt::endl;
1088 outstream << "<SkyFlat>" << (getCoreProperty(SequenceJob::SJ_SkyFlat).toBool() ? "true" : "false") <<
1089 "</SkyFlat>" << Qt::endl;
1090 }
1091 outstream << "</FlatDuration>" << Qt::endl;
1092 outstream << "</Calibration>" << Qt::endl;
1093 outstream << "</Job>" << Qt::endl;
1094}
1095}
1096
void setAlt(dms alt)
Sets Alt, the Altitude.
Definition skypoint.h:194
void setAz(dms az)
Sets Az, the Azimuth.
Definition skypoint.h:230
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
@ SCRIPT_POST_CAPTURE
Script to run after a sequence capture is completed.
Definition ekos.h:176
@ SCRIPT_POST_JOB
Script to run after a sequence job is completed.
Definition ekos.h:177
@ SCRIPT_PRE_CAPTURE
Script to run before a sequence capture is started.
Definition ekos.h:175
@ SCRIPT_PRE_JOB
Script to run before a sequence job is started.
Definition ekos.h:174
QString name(StandardAction id)
QLocale c()
double toDouble(QStringView s, bool *ok) const const
int toInt(QStringView s, bool *ok) const const
QString toString(QDate date, FormatType format) const const
uint toUInt(QStringView s, bool *ok) const const
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QString & append(QChar ch)
QChar * data()
bool isEmpty() const const
QTextStream & endl(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:58:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.