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 <KNotifications/KNotification>
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
39 sharedState.reset(new CaptureModuleState);
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,
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,
57 const QString &targetName)
58{
59 // initialize the state machine
60 state.reset(new SequenceJobState(sharedState));
61
62 loadFrom(root, targetName, jobType, sharedState);
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
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() +
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
156QStringList SequenceJob::frameTypes() const
157{
158 if (!devices->getActiveCamera())
159 return QStringList({"Light", "Bias", "Dark", "Flat"});
160
161 ISD::CameraChip *tChip = devices->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD);
162
163 return tChip->getFrameTypes();
164}
165
166QStringList SequenceJob::filterLabels() const
167{
168 if (devices->getFilterManager().isNull())
169 return QStringList();
170
171 return devices->getFilterManager()->getFilterLabels();
172
173}
174
175void SequenceJob::connectDeviceAdaptor()
176{
177 devices->setCurrentSequenceJobState(state);
178 // connect state machine with device adaptor
179 connect(state.data(), &SequenceJobState::readCurrentState, devices.data(),
180 &CaptureDeviceAdaptor::readCurrentState);
181 connect(state.data(), &SequenceJobState::flatSyncFocus, devices.data(),
182 &CaptureDeviceAdaptor::flatSyncFocus);
183 // connect device adaptor with state machine
184 connect(devices.data(), &CaptureDeviceAdaptor::flatSyncFocusChanged, state.data(),
185 &SequenceJobState::flatSyncFocusChanged);
186}
187
188void SequenceJob::disconnectDeviceAdaptor()
189{
190 devices->disconnectDevices(state.data());
191 disconnect(state.data(), &SequenceJobState::readCurrentState, devices.data(),
192 &CaptureDeviceAdaptor::readCurrentState);
193 disconnect(state.data(), &SequenceJobState::flatSyncFocus, devices.data(),
194 &CaptureDeviceAdaptor::flatSyncFocus);
195 disconnect(devices.data(), &CaptureDeviceAdaptor::flatSyncFocusChanged, state.data(),
196 &SequenceJobState::flatSyncFocusChanged);
197}
198
199void SequenceJob::startCapturing(bool autofocusReady, FITSMode mode)
200{
201 state->initCapture(getFrameType(), jobType() == SequenceJob::JOBTYPE_PREVIEW, autofocusReady, mode);
202}
203
204void SequenceJob::capture(FITSMode mode)
205{
206 if (!devices.data()->getActiveCamera() || !devices.data()->getActiveChip())
207 return;
208
209 // initialize the log entry
210 QString logentry = QString("Capture exposure = %1 sec, type = %2").arg(getCoreProperty(SJ_Exposure).toDouble()).arg(
211 CCDFrameTypeNames[getFrameType()]);
212 logentry.append(QString(", filter = %1, upload mode = %2").arg(getCoreProperty(SJ_Filter).toString()).arg(getUploadMode()));
213
214 devices.data()->getActiveChip()->setBatchMode(jobType() != SequenceJob::JOBTYPE_PREVIEW);
215 devices.data()->getActiveCamera()->setSeqPrefix(getCoreProperty(SJ_FullPrefix).toString());
216 logentry.append(QString(", batch mode = %1, seq prefix = %2").arg(jobType() != SequenceJob::JOBTYPE_PREVIEW ? "true" :
217 "false").arg(getCoreProperty(SJ_FullPrefix).toString()));
218
219 if (jobType() == SequenceJob::JOBTYPE_PREVIEW)
220 {
221 if (devices.data()->getActiveCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
222 devices.data()->getActiveCamera()->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
223 }
224 else
225 devices.data()->getActiveCamera()->setUploadMode(m_UploadMode);
226
228 while (i.hasNext())
229 {
230 i.next();
231 auto customProp = devices.data()->getActiveCamera()->getProperty(i.key());
232 if (customProp)
233 {
234 QMap<QString, QVariant> elements = i.value();
236
237 switch (customProp.getType())
238 {
239 case INDI_SWITCH:
240 {
241 auto sp = customProp.getSwitch();
242 while (j.hasNext())
243 {
244 j.next();
245 auto oneSwitch = sp->findWidgetByName(j.key().toLatin1().data());
246 if (oneSwitch)
247 oneSwitch->setState(static_cast<ISState>(j.value().toInt()));
248 }
249 devices.data()->getActiveCamera()->sendNewProperty(sp);
250 }
251 break;
252 case INDI_TEXT:
253 {
254 auto tp = customProp.getText();
255 while (j.hasNext())
256 {
257 j.next();
258 auto oneText = tp->findWidgetByName(j.key().toLatin1().data());
259 if (oneText)
260 oneText->setText(j.value().toString().toLatin1().constData());
261 }
262 devices.data()->getActiveCamera()->sendNewProperty(tp);
263 }
264 break;
265 case INDI_NUMBER:
266 {
267 auto np = customProp.getNumber();
268 while (j.hasNext())
269 {
270 j.next();
271 auto oneNumber = np->findWidgetByName(j.key().toLatin1().data());
272 if (oneNumber)
273 oneNumber->setValue(j.value().toDouble());
274 }
275 devices.data()->getActiveCamera()->sendNewProperty(np);
276 }
277 break;
278 default:
279 continue;
280 }
281 }
282 }
283
284 const auto remoteFormatDirectory = getCoreProperty(SJ_RemoteFormatDirectory).toString();
285 const auto remoteFormatFilename = getCoreProperty(SJ_RemoteFormatFilename).toString();
286 if (devices.data()->getActiveChip()->isBatchMode() &&
287 remoteFormatDirectory.isEmpty() == false &&
288 remoteFormatFilename.isEmpty() == false)
289 {
290 devices.data()->getActiveCamera()->updateUploadSettings(remoteFormatDirectory, remoteFormatFilename);
291 if (getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
292 logentry.append(QString(", remote dir = %1, remote format = %2").arg(remoteFormatDirectory).arg(remoteFormatFilename));
293 }
294
295 const int ISOIndex = getCoreProperty(SJ_ISOIndex).toInt();
296 if (ISOIndex != -1)
297 {
298 logentry.append(QString(", ISO index = %1").arg(ISOIndex));
299 if (ISOIndex != devices.data()->getActiveChip()->getISOIndex())
300 devices.data()->getActiveChip()->setISOIndex(ISOIndex);
301 }
302
303 const auto gain = getCoreProperty(SJ_Gain).toDouble();
304 if (gain >= 0)
305 {
306 logentry.append(QString(", gain = %1").arg(gain));
307 devices.data()->getActiveCamera()->setGain(gain);
308 }
309
310 const auto offset = getCoreProperty(SJ_Offset).toDouble();
311 if (offset >= 0)
312 {
313 logentry.append(QString(", offset = %1").arg(offset));
314 devices.data()->getActiveCamera()->setOffset(offset);
315 }
316
317 devices.data()->getActiveCamera()->setCaptureFormat(getCoreProperty(SJ_Format).toString());
318 devices.data()->getActiveCamera()->setEncodingFormat(getCoreProperty(SJ_Encoding).toString());
319 devices.data()->getActiveChip()->setFrameType(getFrameType());
320 logentry.append(QString(", format = %1, encoding = %2").arg(getCoreProperty(SJ_Format).toString()).arg(getCoreProperty(
321 SJ_Encoding).toString()));
322
323 // Only attempt to set ROI and Binning if CCD transfer format is FITS or XISF
324 int currentBinX = 1, currentBinY = 1;
325 devices.data()->getActiveChip()->getBinning(&currentBinX, &currentBinY);
326
327 const auto binning = getCoreProperty(SJ_Binning).toPoint();
328 // N.B. Always set binning _before_ setting frame because if the subframed image
329 // is problematic in 1x1 but works fine for 2x2, then it would fail it was set first
330 // So setting binning first always ensures this will work.
331 if (devices.data()->getActiveChip()->canBin())
332 {
333 if (devices.data()->getActiveChip()->setBinning(binning.x(), binning.y()) == false)
334 {
335 qCWarning(KSTARS_EKOS_CAPTURE()) << "Cannot set binning to " << "x =" << binning.x() << ", y =" << binning.y();
336 setStatus(JOB_ERROR);
337 emit captureStarted(CaptureModuleState::CAPTURE_BIN_ERROR);
338 }
339 else
340 logentry.append(QString(", binning = %1x%2").arg(binning.x()).arg(binning.y()));
341 }
342 else
343 logentry.append(QString(", Cannot bin"));
344
345
346 const auto roi = getCoreProperty(SJ_ROI).toRect();
347
348 if (devices.data()->getActiveChip()->canSubframe())
349 {
350 if ((roi.width() > 0 && roi.height() > 0) && devices.data()->getActiveChip()->setFrame(roi.x(),
351 roi.y(),
352 roi.width(),
353 roi.height(),
354 currentBinX != binning.x()) == false)
355 {
356 qCWarning(KSTARS_EKOS_CAPTURE()) << "Cannot set ROI to " << "x =" << roi.x() << ", y =" << roi.y() << ", widht =" <<
357 roi.width() << "height =" << roi.height();
358 setStatus(JOB_ERROR);
359 emit captureStarted(CaptureModuleState::CAPTURE_FRAME_ERROR);
360 }
361 else
362 logentry.append(QString(", ROI = (%1+%2, %3+%4)").arg(roi.x()).arg(roi.width()).arg(roi.y()).arg(roi.width()));
363 }
364 else
365 logentry.append(", Cannot subframe");
366
367 // In case FITS Viewer is not enabled. Then for flat frames, we still need to keep the data
368 // otherwise INDI CCD would simply discard loading the data in batch mode as the data are already
369 // saved to disk and since no extra processing is required, FITSData is not loaded up with the data.
370 // But in case of automatically calculated flat frames, we need FITSData.
371 // Therefore, we need to explicitly set mode to FITS_CALIBRATE so that FITSData is generated.
372 devices.data()->getActiveChip()->setCaptureMode(mode);
373 devices.data()->getActiveChip()->setCaptureFilter(FITS_NONE);
374
375 setStatus(getStatus());
376
377 const auto exposure = getCoreProperty(SJ_Exposure).toDouble();
378 m_ExposeLeft = exposure;
379 devices.data()->getActiveChip()->capture(exposure);
380 // create log entry with settings
381 qCInfo(KSTARS_EKOS_CAPTURE()) << logentry;
382
383 emit captureStarted(CaptureModuleState::CAPTURE_OK);
384}
385
386void SequenceJob::setTargetFilter(int pos, const QString &name)
387{
388 state->targetFilterID = pos;
389 setCoreProperty(SJ_Filter, name);
390}
391
392double SequenceJob::getExposeLeft() const
393{
394 return m_ExposeLeft;
395}
396
397void SequenceJob::setExposeLeft(double value)
398{
399 m_ExposeLeft = value;
400}
401
402
403int SequenceJob::getCaptureRetires() const
404{
405 return m_CaptureRetires;
406}
407
408void SequenceJob::setCaptureRetires(int value)
409{
410 m_CaptureRetires = value;
411}
412
413int SequenceJob::getCurrentFilter() const
414{
415 return state->m_CaptureModuleState->currentFilterID;
416}
417
418ISD::Mount::PierSide SequenceJob::getPierSide() const
419{
420 return state->m_CaptureModuleState->getPierSide();
421}
422
423// Setter: Set upload mode
424void SequenceJob::setUploadMode(ISD::Camera::UploadMode value)
425{
426 m_UploadMode = value;
427}
428// Getter: get upload mode
429ISD::Camera::UploadMode SequenceJob::getUploadMode() const
430{
431 return m_UploadMode;
432}
433
434// Setter: Set flat field source
435void SequenceJob::setCalibrationPreAction(uint32_t value)
436{
437 state->m_CalibrationPreAction = value;
438}
439// Getter: Get calibration pre action
440uint32_t SequenceJob::getCalibrationPreAction() const
441{
442 return state->m_CalibrationPreAction;
443}
444
445void SequenceJob::setWallCoord(const SkyPoint &value)
446{
447 state->wallCoord = value;
448}
449
450const SkyPoint &SequenceJob::getWallCoord() const
451{
452 return state->wallCoord;
453}
454
455// Setter: Set flat field duration
456void SequenceJob::setFlatFieldDuration(FlatFieldDuration value)
457{
458 m_FlatFieldDuration = value;
459}
460
461// Getter: Get flat field duration
462FlatFieldDuration SequenceJob::getFlatFieldDuration() const
463{
464 return m_FlatFieldDuration;
465}
466
467void SequenceJob::setJobProgressIgnored(bool value)
468{
469 m_JobProgressIgnored = value;
470}
471
472bool SequenceJob::getJobProgressIgnored() const
473{
474 return m_JobProgressIgnored;
475}
476
477void SequenceJob::updateDeviceStates()
478{
479 setLightBox(devices->lightBox());
480 addMount(devices->mount());
481 setDome(devices->dome());
482 setDustCap(devices->dustCap());
483}
484
485void SequenceJob::setLightBox(ISD::LightBox * lightBox)
486{
487 state->m_CaptureModuleState->hasLightBox = (lightBox != nullptr);
488}
489
490void SequenceJob::setDustCap(ISD::DustCap * dustCap)
491{
492 state->m_CaptureModuleState->hasDustCap = (dustCap != nullptr);
493}
494
495void SequenceJob::addMount(ISD::Mount * scope)
496{
497 state->m_CaptureModuleState->hasTelescope = (scope != nullptr);
498}
499
500void SequenceJob::setDome(ISD::Dome * dome)
501{
502 state->m_CaptureModuleState->hasDome = (dome != nullptr);
503}
504
505double SequenceJob::currentTemperature() const
506{
507 return devices->cameraTemperature();
508}
509
510double SequenceJob::currentGain() const
511{
512 return devices->cameraGain();
513}
514
515double SequenceJob::currentOffset() const
516{
517 return devices->cameraOffset();
518}
519
520void SequenceJob::prepareCapture()
521{
522 // simply forward it to the state machine
523 switch (getFrameType())
524 {
525 case FRAME_LIGHT:
526 state->prepareLightFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
527 jobType() == SequenceJob::JOBTYPE_PREVIEW);
528 break;
529 case FRAME_FLAT:
530 state->prepareFlatFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
531 jobType() == SequenceJob::JOBTYPE_PREVIEW);
532 break;
533 case FRAME_DARK:
534 state->prepareDarkFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
535 jobType() == SequenceJob::JOBTYPE_PREVIEW);
536 break;
537 case FRAME_BIAS:
538 state->prepareBiasFrameCapture(getCoreProperty(SJ_EnforceTemperature).toBool(),
539 jobType() == SequenceJob::JOBTYPE_PREVIEW);
540 break;
541 default:
542 // not refactored yet, immediately completed
543 processPrepareComplete();
544 break;
545 }
546}
547
548void SequenceJob::processPrepareComplete(bool success)
549{
550 emit prepareComplete(success);
551}
552
553void SequenceJob::processAbortCapture()
554{
555 disconnectDeviceAdaptor();
556 emit abortCapture();
557}
558
559IPState SequenceJob::checkFlatFramePendingTasksCompleted()
560{
561 // no further checks necessary
562 return IPS_OK;
563}
564
565void SequenceJob::setCoreProperty(PropertyID id, const QVariant &value)
566{
567 // Handle special cases
568 switch (id)
569 {
570 case SJ_RemoteDirectory:
571 {
572 auto remoteDir = value.toString();
573 if (remoteDir.endsWith('/'))
574 {
575 remoteDir.chop(1);
576 m_CoreProperties[id] = remoteDir;
577 }
578 }
579 break;
580
581 default:
582 break;
583 }
584 // store value
585 m_CoreProperties[id] = value;
586}
587
588QVariant SequenceJob::getCoreProperty(PropertyID id) const
589{
590 return m_CoreProperties[id];
591}
592
593void SequenceJob::loadFrom(XMLEle *root, const QString &targetName, SequenceJobType jobType,
595{
596 setJobType(jobType);
597
598 // Set default property values
599 m_CoreProperties[SJ_Exposure] = -1;
600 m_CoreProperties[SJ_Gain] = -1;
601 m_CoreProperties[SJ_Offset] = -1;
602 m_CoreProperties[SJ_ISOIndex] = -1;
603 m_CoreProperties[SJ_Count] = -1;
604 m_CoreProperties[SJ_Delay] = -1;
605 m_CoreProperties[SJ_Binning] = QPoint(1, 1);
606 m_CoreProperties[SJ_ROI] = QRect(0, 0, 0, 0);
607 m_CoreProperties[SJ_EnforceTemperature] = false;
608 m_CoreProperties[SJ_GuiderActive] = false;
609 m_CoreProperties[SJ_DitherPerJobFrequency] = 0;
610 m_CoreProperties[SJ_Encoding] = "FITS";
611
612 // targetName overrides values from the XML document
613 if (targetName != "")
614 setCoreProperty(SequenceJob::SJ_TargetName, targetName);
615
616 if (root == nullptr)
617 return;
618
619 bool isDarkFlat = false;
620
622 XMLEle * ep;
623 XMLEle * subEP;
624 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
625 {
626 if (!strcmp(tagXMLEle(ep), "Exposure"))
627 setCoreProperty(SequenceJob::SJ_Exposure, cLocale.toDouble(pcdataXMLEle(ep)));
628 else if (!strcmp(tagXMLEle(ep), "Format"))
629 setCoreProperty(SequenceJob::SJ_Format, pcdataXMLEle(ep));
630 else if (!strcmp(tagXMLEle(ep), "Encoding"))
631 {
632 setCoreProperty(SequenceJob::SJ_Encoding, pcdataXMLEle(ep));
633 }
634 else if (!strcmp(tagXMLEle(ep), "Binning"))
635 {
636 QPoint binning(1, 1);
637 subEP = findXMLEle(ep, "X");
638 if (subEP)
639 binning.setX(cLocale.toInt(pcdataXMLEle(subEP)));
640 subEP = findXMLEle(ep, "Y");
641 if (subEP)
642 binning.setY(cLocale.toInt(pcdataXMLEle(subEP)));
643
644 setCoreProperty(SequenceJob::SJ_Binning, binning);
645 }
646 else if (!strcmp(tagXMLEle(ep), "Frame"))
647 {
648 QRect roi(0, 0, 0, 0);
649 subEP = findXMLEle(ep, "X");
650 if (subEP)
652 subEP = findXMLEle(ep, "Y");
653 if (subEP)
655 subEP = findXMLEle(ep, "W");
656 if (subEP)
657 roi.setWidth(cLocale.toInt(pcdataXMLEle(subEP)));
658 subEP = findXMLEle(ep, "H");
659 if (subEP)
660 roi.setHeight(cLocale.toInt(pcdataXMLEle(subEP)));
661
662 setCoreProperty(SequenceJob::SJ_ROI, roi);
663 }
664 else if (!strcmp(tagXMLEle(ep), "Temperature"))
665 {
666 setTargetTemperature(cLocale.toDouble(pcdataXMLEle(ep)));
667
668 // If force attribute exist, we change cameraTemperatureS, otherwise do nothing.
669 if (!strcmp(findXMLAttValu(ep, "force"), "true"))
670 setCoreProperty(SequenceJob::SJ_EnforceTemperature, true);
671 else if (!strcmp(findXMLAttValu(ep, "force"), "false"))
672 setCoreProperty(SequenceJob::SJ_EnforceTemperature, false);
673 }
674 else if (!strcmp(tagXMLEle(ep), "Filter"))
675 {
676 const auto name = pcdataXMLEle(ep);
677 const auto index = std::max(1, filterLabels().indexOf(name) + 1);
678 setTargetFilter(index, name);
679 }
680 else if (!strcmp(tagXMLEle(ep), "Type"))
681 {
682 int index = frameTypes().indexOf(pcdataXMLEle(ep));
683 setFrameType(static_cast<CCDFrameType>(qMax(0, index)));
684 }
685 else if (!strcmp(tagXMLEle(ep), "TargetName"))
686 {
687 auto jobTarget = pcdataXMLEle(ep);
688
689 if (targetName == "")
690 // use the target from the XML document
691 setCoreProperty(SequenceJob::SJ_TargetName, jobTarget);
692 else if (strcmp(jobTarget, "") != 0)
693 // issue a warning that target from the XML document is ignored
694 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job target name %1 ignored.").arg(jobTarget);
695 }
696 else if (!strcmp(tagXMLEle(ep), "Prefix"))
697 {
698 // RawPrefix is outdated and will be ignored
699 subEP = findXMLEle(ep, "RawPrefix");
700 if (subEP)
701 {
703
704 if (targetName == "")
705 // use the target from the XML document
706 setCoreProperty(SequenceJob::SJ_TargetName, jobTarget);
707 else if (strcmp(jobTarget, "") != 0)
708 // issue a warning that target from the XML document is ignored
709 qWarning(KSTARS_EKOS_CAPTURE) << QString("Sequence job raw prefix %1 ignored.").arg(jobTarget);
710 }
711 bool filterEnabled = false, expEnabled = false, tsEnabled = false;
712 subEP = findXMLEle(ep, "FilterEnabled");
713 if (subEP)
715 subEP = findXMLEle(ep, "ExpEnabled");
716 if (subEP)
718 subEP = findXMLEle(ep, "TimeStampEnabled");
719 if (subEP)
721 // build default format
722 setCoreProperty(SequenceJob::SJ_PlaceholderFormat,
723 PlaceholderPath::defaultFormat(filterEnabled, expEnabled, tsEnabled));
724 }
725 else if (!strcmp(tagXMLEle(ep), "Count"))
726 {
727 setCoreProperty(SequenceJob::SJ_Count, cLocale.toInt(pcdataXMLEle(ep)));
728 }
729 else if (!strcmp(tagXMLEle(ep), "Delay"))
730 {
731 setCoreProperty(SequenceJob::SJ_Delay, cLocale.toInt(pcdataXMLEle(ep)) * 1000);
732 }
733 else if (!strcmp(tagXMLEle(ep), "PostCaptureScript"))
734 {
735 m_Scripts[SCRIPT_POST_CAPTURE] = pcdataXMLEle(ep);
736 }
737 else if (!strcmp(tagXMLEle(ep), "PreCaptureScript"))
738 {
739 m_Scripts[SCRIPT_PRE_CAPTURE] = pcdataXMLEle(ep);
740 }
741 else if (!strcmp(tagXMLEle(ep), "PostJobScript"))
742 {
743 m_Scripts[SCRIPT_POST_JOB] = pcdataXMLEle(ep);
744 }
745 else if (!strcmp(tagXMLEle(ep), "PreJobScript"))
746 {
747 m_Scripts[SCRIPT_PRE_JOB] = pcdataXMLEle(ep);
748 }
749 else if (!strcmp(tagXMLEle(ep), "GuideDitherPerJob"))
750 {
751 setCoreProperty(SequenceJob::SJ_DitherPerJobFrequency, cLocale.toInt(pcdataXMLEle(ep)));
752 }
753 else if (!strcmp(tagXMLEle(ep), "FITSDirectory"))
754 {
755 setCoreProperty(SequenceJob::SJ_LocalDirectory, pcdataXMLEle(ep));
756 }
757 else if (!strcmp(tagXMLEle(ep), "PlaceholderFormat"))
758 {
759 setCoreProperty(SequenceJob::SJ_PlaceholderFormat, pcdataXMLEle(ep));
760 }
761 else if (!strcmp(tagXMLEle(ep), "PlaceholderSuffix"))
762 {
763 setCoreProperty(SequenceJob::SJ_PlaceholderSuffix, cLocale.toUInt(pcdataXMLEle(ep)));
764 }
765 else if (!strcmp(tagXMLEle(ep), "RemoteDirectory"))
766 {
767 setCoreProperty(SequenceJob::SJ_RemoteDirectory, pcdataXMLEle(ep));
768 }
769 else if (!strcmp(tagXMLEle(ep), "UploadMode"))
770 {
771 setUploadMode(static_cast<ISD::Camera::UploadMode>(cLocale.toInt(pcdataXMLEle(ep))));
772 }
773 else if (!strcmp(tagXMLEle(ep), "ISOIndex"))
774 {
775 setISO(cLocale.toInt(pcdataXMLEle(ep)));
776 }
777 else if (!strcmp(tagXMLEle(ep), "Rotation"))
778 {
779 setTargetRotation(cLocale.toDouble(pcdataXMLEle(ep)));
780 }
781 else if (!strcmp(tagXMLEle(ep), "Properties"))
782 {
784
785 for (subEP = nextXMLEle(ep, 1); subEP != nullptr; subEP = nextXMLEle(ep, 0))
786 {
788 XMLEle * oneElement = nullptr;
789 for (oneElement = nextXMLEle(subEP, 1); oneElement != nullptr; oneElement = nextXMLEle(subEP, 0))
790 {
791 const char * name = findXMLAttValu(oneElement, "name");
792 bool ok = false;
793 // String
795 // Try to load it as double
796 auto value = cLocale.toDouble(xmlValue, &ok);
797 if (ok)
798 elements[name] = value;
799 else
800 elements[name] = xmlValue;
801 }
802
803 const char * name = findXMLAttValu(subEP, "name");
804 propertyMap[name] = elements;
805 }
806
807 setCustomProperties(propertyMap);
808 // read the gain and offset values from the custom properties
809 setCoreProperty(SequenceJob::SJ_Gain, devices->cameraGain(propertyMap));
810 setCoreProperty(SequenceJob::SJ_Offset, devices->cameraOffset(propertyMap));
811 }
812 else if (!strcmp(tagXMLEle(ep), "Calibration"))
813 {
814 // SQ_FORMAT_VERSION >= 2.7
815 subEP = findXMLEle(ep, "PreAction");
816 if (subEP)
817 {
818 XMLEle * typeEP = findXMLEle(subEP, "Type");
819 if (typeEP)
820 {
821 setCalibrationPreAction(cLocale.toUInt(pcdataXMLEle(typeEP)));
822 if (getCalibrationPreAction() & ACTION_WALL)
823 {
824 XMLEle * azEP = findXMLEle(subEP, "Az");
825 XMLEle * altEP = findXMLEle(subEP, "Alt");
826
827 if (azEP && altEP)
828 {
829 setCalibrationPreAction((getCalibrationPreAction() & ~ACTION_PARK_MOUNT) | ACTION_WALL);
830 SkyPoint wallCoord;
831 wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP)));
832 wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP)));
833 setWallCoord(wallCoord);
834 }
835 else
836 {
837 qCWarning(KSTARS_EKOS_CAPTURE) << "Wall position coordinates missing, disabling slew to wall position action.";
838 setCalibrationPreAction((getCalibrationPreAction() & ~ACTION_WALL) | ACTION_NONE);
839 }
840 }
841 }
842 }
843
844 // SQ_FORMAT_VERSION < 2.7
845 subEP = findXMLEle(ep, "FlatSource");
846 if (subEP)
847 {
848 XMLEle * typeEP = findXMLEle(subEP, "Type");
849 if (typeEP)
850 {
851 // default
852 setCalibrationPreAction(ACTION_NONE);
853 if (!strcmp(pcdataXMLEle(typeEP), "Wall"))
854 {
855 XMLEle * azEP = findXMLEle(subEP, "Az");
856 XMLEle * altEP = findXMLEle(subEP, "Alt");
857
858 if (azEP && altEP)
859 {
860 setCalibrationPreAction((getCalibrationPreAction() & ~ACTION_PARK_MOUNT) | ACTION_WALL);
861 SkyPoint wallCoord;
862 wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP)));
863 wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP)));
864 setWallCoord(wallCoord);
865 }
866 }
867 }
868 }
869
870 // SQ_FORMAT_VERSION < 2.7
871 subEP = findXMLEle(ep, "PreMountPark");
872 if (subEP && !strcmp(pcdataXMLEle(subEP), "True"))
873 setCalibrationPreAction(getCalibrationPreAction() | ACTION_PARK_MOUNT);
874
875 // SQ_FORMAT_VERSION < 2.7
876 subEP = findXMLEle(ep, "PreDomePark");
877 if (subEP && !strcmp(pcdataXMLEle(subEP), "True"))
878 setCalibrationPreAction(getCalibrationPreAction() | ACTION_PARK_DOME);
879
880 subEP = findXMLEle(ep, "FlatDuration");
881 if (subEP)
882 {
883 const char * dark = findXMLAttValu(subEP, "dark");
884 isDarkFlat = !strcmp(dark, "true");
885
886 XMLEle * typeEP = findXMLEle(subEP, "Type");
887 if (typeEP)
888 {
889 if (!strcmp(pcdataXMLEle(typeEP), "Manual"))
890 setFlatFieldDuration(DURATION_MANUAL);
891 }
892
893 XMLEle * aduEP = findXMLEle(subEP, "Value");
894 if (aduEP)
895 {
896 setFlatFieldDuration(DURATION_ADU);
897 setCoreProperty(SequenceJob::SJ_TargetADU, QVariant(cLocale.toDouble(pcdataXMLEle(aduEP))));
898 }
899
900 aduEP = findXMLEle(subEP, "Tolerance");
901 if (aduEP)
902 {
903 setCoreProperty(SequenceJob::SJ_TargetADUTolerance, QVariant(cLocale.toDouble(pcdataXMLEle(aduEP))));
904 }
905 aduEP = findXMLEle(subEP, "SkyFlat");
906 if (aduEP)
907 {
908 setCoreProperty(SequenceJob::SJ_SkyFlat, (bool)!strcmp(pcdataXMLEle(aduEP), "true"));
909 }
910 }
911 }
912 }
913 if(isDarkFlat)
914 setJobType(SequenceJob::JOBTYPE_DARKFLAT);
915}
916
917void SequenceJob::saveTo(QTextStream &outstream, const QLocale &cLocale) const
918{
919 auto roi = getCoreProperty(SequenceJob::SJ_ROI).toRect();
920
921 outstream << "<Job>" << Qt::endl;
922
923 outstream << "<Exposure>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Exposure).toDouble()) << "</Exposure>" <<
924 Qt::endl;
925 outstream << "<Format>" << getCoreProperty(SequenceJob::SJ_Format).toString() << "</Format>" << Qt::endl;
926 outstream << "<Encoding>" << getCoreProperty(SequenceJob::SJ_Encoding).toString() << "</Encoding>" << Qt::endl;
927 outstream << "<Binning>" << Qt::endl;
928 outstream << "<X>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Binning).toPoint().x()) << "</X>" << Qt::endl;
929 outstream << "<Y>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Binning).toPoint().y()) << "</Y>" << Qt::endl;
930 outstream << "</Binning>" << Qt::endl;
931 outstream << "<Frame>" << Qt::endl;
932 outstream << "<X>" << cLocale.toString(roi.x()) << "</X>" << Qt::endl;
933 outstream << "<Y>" << cLocale.toString(roi.y()) << "</Y>" << Qt::endl;
934 outstream << "<W>" << cLocale.toString(roi.width()) << "</W>" << Qt::endl;
935 outstream << "<H>" << cLocale.toString(roi.height()) << "</H>" << Qt::endl;
936 outstream << "</Frame>" << Qt::endl;
937 if (getTargetTemperature() != Ekos::INVALID_VALUE)
938 outstream << "<Temperature force='" << (getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool() ? "true" :
939 "false") << "'>"
940 << cLocale.toString(getTargetTemperature()) << "</Temperature>" << Qt::endl;
941 if (getTargetFilter() >= 0)
942 outstream << "<Filter>" << getCoreProperty(SequenceJob::SJ_Filter).toString() << "</Filter>" << Qt::endl;
943 outstream << "<Type>" << frameTypes()[getFrameType()] << "</Type>" << Qt::endl;
944 outstream << "<Count>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Count).toInt()) << "</Count>" << Qt::endl;
945 // ms to seconds
946 outstream << "<Delay>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_Delay).toInt() / 1000.0) << "</Delay>" <<
947 Qt::endl;
948 if (getCoreProperty(SequenceJob::SJ_TargetName) != "")
949 outstream << "<TargetName>" << getCoreProperty(SequenceJob::SJ_TargetName).toString() << "</TargetName>" << Qt::endl;
950 if (getScript(SCRIPT_PRE_CAPTURE).isEmpty() == false)
951 outstream << "<PreCaptureScript>" << getScript(SCRIPT_PRE_CAPTURE) << "</PreCaptureScript>" << Qt::endl;
952 if (getScript(SCRIPT_POST_CAPTURE).isEmpty() == false)
953 outstream << "<PostCaptureScript>" << getScript(SCRIPT_POST_CAPTURE) << "</PostCaptureScript>" << Qt::endl;
954 if (getScript(SCRIPT_PRE_JOB).isEmpty() == false)
955 outstream << "<PreJobScript>" << getScript(SCRIPT_PRE_JOB) << "</PreJobScript>" << Qt::endl;
956 if (getScript(SCRIPT_POST_JOB).isEmpty() == false)
957 outstream << "<PostJobScript>" << getScript(SCRIPT_POST_JOB) << "</PostJobScript>" << Qt::endl;
958 outstream << "<GuideDitherPerJob>"
959 << cLocale.toString(getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt()) << "</GuideDitherPerJob>" <<
960 Qt::endl;
961 outstream << "<FITSDirectory>" << getCoreProperty(SequenceJob::SJ_LocalDirectory).toString() << "</FITSDirectory>" <<
962 Qt::endl;
963 outstream << "<PlaceholderFormat>" << getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString() <<
964 "</PlaceholderFormat>" <<
965 Qt::endl;
966 outstream << "<PlaceholderSuffix>" << getCoreProperty(SequenceJob::SJ_PlaceholderSuffix).toUInt() <<
967 "</PlaceholderSuffix>" <<
968 Qt::endl;
969 outstream << "<UploadMode>" << getUploadMode() << "</UploadMode>" << Qt::endl;
970 if (getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString().isEmpty() == false)
971 outstream << "<RemoteDirectory>" << getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString() << "</RemoteDirectory>"
972 << Qt::endl;
973 if (getCoreProperty(SequenceJob::SJ_ISOIndex).toInt() != -1)
974 outstream << "<ISOIndex>" << (getCoreProperty(SequenceJob::SJ_ISOIndex).toInt()) << "</ISOIndex>" << Qt::endl;
975 if (getTargetRotation() != Ekos::INVALID_VALUE)
976 outstream << "<Rotation>" << (getTargetRotation()) << "</Rotation>" << Qt::endl;
978 outstream << "<Properties>" << Qt::endl;
979 while (customIter.hasNext())
980 {
981 customIter.next();
982 outstream << "<PropertyVector name='" << customIter.key() << "'>" << Qt::endl;
983 QMap<QString, QVariant> elements = customIter.value();
984 QMapIterator<QString, QVariant> iter(elements);
985 while (iter.hasNext())
986 {
987 iter.next();
988 if (iter.value().type() == QVariant::String)
989 {
990 outstream << "<OneElement name='" << iter.key()
991 << "'>" << iter.value().toString() << "</OneElement>" << Qt::endl;
992 }
993 else
994 {
995 outstream << "<OneElement name='" << iter.key()
996 << "'>" << iter.value().toDouble() << "</OneElement>" << Qt::endl;
997 }
998 }
999 outstream << "</PropertyVector>" << Qt::endl;
1000 }
1001 outstream << "</Properties>" << Qt::endl;
1002
1003 outstream << "<Calibration>" << Qt::endl;
1004 outstream << "<PreAction>" << Qt::endl;
1005 outstream << QString("<Type>%1</Type>").arg(getCalibrationPreAction()) << Qt::endl;
1006 if (getCalibrationPreAction() & ACTION_WALL)
1007 {
1008 outstream << "<Az>" << cLocale.toString(getWallCoord().az().Degrees()) << "</Az>" << Qt::endl;
1009 outstream << "<Alt>" << cLocale.toString(getWallCoord().alt().Degrees()) << "</Alt>" << Qt::endl;
1010 }
1011 outstream << "</PreAction>" << Qt::endl;
1012
1013 outstream << "<FlatDuration dark='" << (jobType() == SequenceJob::JOBTYPE_DARKFLAT ? "true" : "false")
1014 << "'>" << Qt::endl;
1015 if (getFlatFieldDuration() == DURATION_MANUAL)
1016 outstream << "<Type>Manual</Type>" << Qt::endl;
1017 else
1018 {
1019 outstream << "<Type>ADU</Type>" << Qt::endl;
1020 outstream << "<Value>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_TargetADU).toDouble()) << "</Value>" <<
1021 Qt::endl;
1022 outstream << "<Tolerance>" << cLocale.toString(getCoreProperty(SequenceJob::SJ_TargetADUTolerance).toDouble()) <<
1023 "</Tolerance>" << Qt::endl;
1024 outstream << "<SkyFlat>" << (getCoreProperty(SequenceJob::SJ_SkyFlat).toBool() ? "true" : "false") <<
1025 "</SkyFlat>" << Qt::endl;
1026 }
1027 outstream << "</FlatDuration>" << Qt::endl;
1028 outstream << "</Calibration>" << Qt::endl;
1029 outstream << "</Job>" << Qt::endl;
1030}
1031}
1032
CameraChip class controls a particular chip in camera.
Class handles control of INDI dome devices.
Definition indidome.h:23
Handles operation of a remotely controlled dust cover cap.
Definition indidustcap.h:23
Handles operation of a remotely controlled light box.
device handle controlling Mounts.
Definition indimount.h:27
The sky coordinates of a point in the sky.
Definition skypoint.h:45
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:78
@ 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(StandardShortcut id)
Int toInt() const const
QLocale c()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
int x() const const
T * data() const const
QString arg(Args &&... args) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QTextStream & endl(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QPoint toPoint() const const
QRect toRect() const const
QString toString() const const
uint toUInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.