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

KDE's Doxygen guidelines are available online.