Kstars

indicamera.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 "indicamera.h"
8#include "indicamerachip.h"
9
10#include "config-kstars.h"
11
12#include "indi_debug.h"
13
14#include "clientmanager.h"
15#include "kstars.h"
16#include "Options.h"
17#include "streamwg.h"
18//#include "ekos/manager.h"
19#ifdef HAVE_CFITSIO
20#include "fitsviewer/fitsdata.h"
21#endif
22
23#include <knotification.h>
24#include "auxiliary/ksmessagebox.h"
25#include "ksnotification.h"
26#include <QImageReader>
27#include <QFileInfo>
28#include <QStatusBar>
29#include <QtConcurrent>
30
31#include <basedevice.h>
32
33const QStringList RAWFormats = { "cr2", "cr3", "crw", "nef", "raf", "dng", "arw", "orf" };
34
35const QString getFITSModeStringString(FITSMode mode)
36{
37 return FITSModes[mode].toString();
38}
39
40namespace ISD
41{
42
43Camera::Camera(GenericDevice *parent) : ConcreteDevice(parent)
44{
45 primaryChip.reset(new CameraChip(this, CameraChip::PRIMARY_CCD));
46
47 m_Media.reset(new WSMedia(this));
48 connect(m_Media.get(), &WSMedia::newFile, this, &Camera::setWSBLOB);
49
50 connect(m_Parent->getClientManager(), &ClientManager::newBLOBManager, this, &Camera::setBLOBManager, Qt::UniqueConnection);
51 m_LastNotificationTS = QDateTime::currentDateTime();
52}
53
54Camera::~Camera()
55{
56 if (m_ImageViewerWindow)
57 m_ImageViewerWindow->close();
58 if (fileWriteThread.isRunning())
59 fileWriteThread.waitForFinished();
60 if (fileWriteBuffer != nullptr)
61 delete [] fileWriteBuffer;
62}
63
64void Camera::setBLOBManager(const char *device, INDI::Property prop)
65{
66 if (!prop.getRegistered())
67 return;
68
69 if (getDeviceName() == device)
70 emit newBLOBManager(prop);
71}
72
73void Camera::registerProperty(INDI::Property prop)
74{
75 if (prop.isNameMatch("GUIDER_EXPOSURE"))
76 {
77 HasGuideHead = true;
78 guideChip.reset(new CameraChip(this, CameraChip::GUIDE_CCD));
79 }
80 else if (prop.isNameMatch("CCD_FRAME_TYPE"))
81 {
82 primaryChip->clearFrameTypes();
83
84 for (auto &it : *prop.getSwitch())
85 primaryChip->addFrameLabel(it.getLabel());
86 }
87 else if (prop.isNameMatch("CCD_FRAME"))
88 {
89 auto np = prop.getNumber();
90 if (np && np->getPermission() != IP_RO)
91 primaryChip->setCanSubframe(true);
92 }
93 else if (prop.isNameMatch("GUIDER_FRAME"))
94 {
95 auto np = prop.getNumber();
96 if (np && np->getPermission() != IP_RO)
97 guideChip->setCanSubframe(true);
98 }
99 else if (prop.isNameMatch("CCD_BINNING"))
100 {
101 auto np = prop.getNumber();
102 if (np && np->getPermission() != IP_RO)
103 primaryChip->setCanBin(true);
104 }
105 else if (prop.isNameMatch("GUIDER_BINNING"))
106 {
107 auto np = prop.getNumber();
108 if (np && np->getPermission() != IP_RO)
109 guideChip->setCanBin(true);
110 }
111 else if (prop.isNameMatch("CCD_ABORT_EXPOSURE"))
112 {
113 auto sp = prop.getSwitch();
114 if (sp && sp->getPermission() != IP_RO)
115 primaryChip->setCanAbort(true);
116 }
117 else if (prop.isNameMatch("GUIDER_ABORT_EXPOSURE"))
118 {
119 auto sp = prop.getSwitch();
120 if (sp && sp->getPermission() != IP_RO)
121 guideChip->setCanAbort(true);
122 }
123 else if (prop.isNameMatch("CCD_TEMPERATURE"))
124 {
125 auto np = prop.getNumber();
126 HasCooler = true;
127 CanCool = (np->getPermission() != IP_RO);
128 if (np)
129 emit newTemperatureValue(np->at(0)->getValue());
130 }
131 else if (prop.isNameMatch("CCD_COOLER"))
132 {
133 // Can turn cooling on/off
134 HasCoolerControl = true;
135 }
136 else if (prop.isNameMatch("CCD_VIDEO_STREAM"))
137 {
138 // Has Video Stream
139 HasVideoStream = true;
140 }
141 else if (prop.isNameMatch("CCD_CAPTURE_FORMAT"))
142 {
143 auto sp = prop.getSwitch();
144 if (sp)
145 {
146 m_CaptureFormats.clear();
147 for (const auto &oneSwitch : *sp)
148 m_CaptureFormats << oneSwitch.getLabel();
149
150 m_CaptureFormatIndex = sp->findOnSwitchIndex();
151 }
152 }
153 else if (prop.isNameMatch("CCD_STREAM_ENCODER"))
154 {
155 auto sp = prop.getSwitch();
156 if (sp)
157 {
158 m_StreamEncodings.clear();
159 for (const auto &oneSwitch : *sp)
160 m_StreamEncodings << oneSwitch.getLabel();
161
162 auto format = sp->findOnSwitch();
163 if (format)
164 m_StreamEncoding = format->label;
165 }
166 }
167 else if (prop.isNameMatch("CCD_TRANSFER_FORMAT"))
168 {
169 auto sp = prop.getSwitch();
170 if (sp)
171 {
172 m_EncodingFormats.clear();
173 for (const auto &oneSwitch : *sp)
174 m_EncodingFormats << oneSwitch.getLabel();
175
176 auto format = sp->findOnSwitch();
177 if (format)
178 m_EncodingFormat = format->label;
179 }
180 }
181 else if (prop.isNameMatch("CCD_STREAM_RECORDER"))
182 {
183 auto sp = prop.getSwitch();
184 if (sp)
185 {
186 m_VideoFormats.clear();
187 for (const auto &oneSwitch : *sp)
188 m_VideoFormats << oneSwitch.getLabel();
189
190 auto format = sp->findOnSwitch();
191 if (format)
192 m_StreamRecording = format->label;
193 }
194 }
195 else if (prop.isNameMatch("CCD_EXPOSURE_PRESETS"))
196 {
197 auto svp = prop.getSwitch();
198 if (svp)
199 {
200 bool ok = false;
201 auto separator = QDir::separator();
202 for (const auto &it : *svp)
203 {
204 QString key = QString(it.getLabel());
205 double value = key.toDouble(&ok);
206 if (!ok)
207 {
208 QStringList parts = key.split(separator);
209 if (parts.count() == 2)
210 {
211 bool numOk = false, denOk = false;
212 double numerator = parts[0].toDouble(&numOk);
213 double denominator = parts[1].toDouble(&denOk);
214 if (numOk && denOk && denominator > 0)
215 {
216 ok = true;
217 value = numerator / denominator;
218 }
219 }
220 }
221 if (ok)
222 m_ExposurePresets.insert(key, value);
223
224 double min = 1e6, max = 1e-6;
225 for (auto oneValue : m_ExposurePresets.values())
226 {
227 if (oneValue < min)
228 min = oneValue;
229 if (oneValue > max)
230 max = oneValue;
231 }
232 m_ExposurePresetsMinMax = qMakePair(min, max);
233 }
234 }
235 }
236 else if (prop.isNameMatch("CCD_FAST_TOGGLE"))
237 {
238 auto sp = prop.getSwitch();
239 if (sp)
240 m_FastExposureEnabled = sp->findOnSwitchIndex() == 0;
241 else
242 m_FastExposureEnabled = false;
243 }
244 else if (prop.isNameMatch("TELESCOPE_TYPE"))
245 {
246 auto sp = prop.getSwitch();
247 if (sp)
248 {
249 auto format = sp->findWidgetByName("TELESCOPE_PRIMARY");
250 if (format && format->getState() == ISS_ON)
251 telescopeType = TELESCOPE_PRIMARY;
252 else
253 telescopeType = TELESCOPE_GUIDE;
254 }
255 }
256 else if (prop.isNameMatch("CCD_WEBSOCKET_SETTINGS"))
257 {
258 auto np = prop.getNumber();
259 m_Media->setURL(QUrl(QString("ws://%1:%2").arg(m_Parent->getClientManager()->getHost()).arg(np->at(0)->getValue())));
260 m_Media->connectServer();
261 }
262 else if (prop.isNameMatch("CCD1"))
263 {
264 primaryCCDBLOB = prop;
265 }
266 // try to find gain and/or offset property, if any
267 else if ( (gainN == nullptr || offsetN == nullptr) && prop.getType() == INDI_NUMBER)
268 {
269 // Since gain is spread among multiple property depending on the camera providing it
270 // we need to search in all possible number properties
271 auto controlNP = prop.getNumber();
272 if (controlNP)
273 {
274 for (auto &it : *controlNP)
275 {
276 QString name = QString(it.getName()).toLower();
277 QString label = QString(it.getLabel()).toLower();
278
279 if (name == "gain" || label == "gain")
280 {
281 gainN = &it;
282 gainPerm = controlNP->getPermission();
283 }
284 else if (name == "offset" || label == "offset")
285 {
286 offsetN = &it;
287 offsetPerm = controlNP->getPermission();
288 }
289 }
290 }
291 }
292
293 ConcreteDevice::registerProperty(prop);
294}
295
296void Camera::removeProperty(INDI::Property prop)
297{
298 if (prop.isNameMatch("CCD_WEBSOCKET_SETTINGS"))
299 {
300 m_Media->disconnectServer();
301 }
302}
303
304void Camera::processNumber(INDI::Property prop)
305{
306 auto nvp = prop.getNumber();
307 if (nvp->isNameMatch("CCD_EXPOSURE"))
308 {
309 auto np = nvp->findWidgetByName("CCD_EXPOSURE_VALUE");
310 if (np)
311 emit newExposureValue(primaryChip.get(), np->getValue(), nvp->getState());
312 if (nvp->getState() == IPS_ALERT)
313 emit error(ERROR_CAPTURE);
314 }
315 else if (prop.isNameMatch("CCD_TEMPERATURE"))
316 {
317 HasCooler = true;
318 auto np = nvp->findWidgetByName("CCD_TEMPERATURE_VALUE");
319 if (np)
320 emit newTemperatureValue(np->getValue());
321 }
322 else if (prop.isNameMatch("GUIDER_EXPOSURE"))
323 {
324 auto np = nvp->findWidgetByName("GUIDER_EXPOSURE_VALUE");
325 if (np)
326 emit newExposureValue(guideChip.get(), np->getValue(), nvp->getState());
327 }
328 else if (prop.isNameMatch("FPS"))
329 {
330 emit newFPS(nvp->at(0)->getValue(), nvp->at(1)->getValue());
331 }
332 else if (prop.isNameMatch("CCD_RAPID_GUIDE_DATA"))
333 {
334 if (nvp->getState() == IPS_ALERT)
335 {
336 emit newGuideStarData(primaryChip.get(), -1, -1, -1);
337 }
338 else
339 {
340 double dx = -1, dy = -1, fit = -1;
341
342 auto np = nvp->findWidgetByName("GUIDESTAR_X");
343 if (np)
344 dx = np->getValue();
345 np = nvp->findWidgetByName("GUIDESTAR_Y");
346 if (np)
347 dy = np->getValue();
348 np = nvp->findWidgetByName("GUIDESTAR_FIT");
349 if (np)
350 fit = np->getValue();
351
352 if (dx >= 0 && dy >= 0 && fit >= 0)
353 emit newGuideStarData(primaryChip.get(), dx, dy, fit);
354 }
355 }
356 else if (prop.isNameMatch("GUIDER_RAPID_GUIDE_DATA"))
357 {
358 if (nvp->getState() == IPS_ALERT)
359 {
360 emit newGuideStarData(guideChip.get(), -1, -1, -1);
361 }
362 else
363 {
364 double dx = -1, dy = -1, fit = -1;
365 auto np = nvp->findWidgetByName("GUIDESTAR_X");
366 if (np)
367 dx = np->getValue();
368 np = nvp->findWidgetByName("GUIDESTAR_Y");
369 if (np)
370 dy = np->getValue();
371 np = nvp->findWidgetByName("GUIDESTAR_FIT");
372 if (np)
373 fit = np->getValue();
374
375 if (dx >= 0 && dy >= 0 && fit >= 0)
376 emit newGuideStarData(guideChip.get(), dx, dy, fit);
377 }
378 }
379}
380
381void Camera::processSwitch(INDI::Property prop)
382{
383 auto svp = prop.getSwitch();
384
385 if (svp->isNameMatch("CCD_COOLER"))
386 {
387 // Can turn cooling on/off
388 HasCoolerControl = true;
389 emit coolerToggled(svp->sp[0].s == ISS_ON);
390 }
391 else if (QString(svp->getName()).endsWith("VIDEO_STREAM"))
392 {
393 // If BLOB is not enabled for this camera, then ignore all VIDEO_STREAM calls.
394 if (isBLOBEnabled() == false || m_StreamingEnabled == false)
395 return;
396
397 HasVideoStream = true;
398
399 if (svp->sp[0].s == ISS_ON)
400 {
401 INumberVectorProperty *streamFrame = getNumber("CCD_STREAM_FRAME");
402 INumber *w = nullptr, *h = nullptr;
403
404 if (streamFrame)
405 {
406 w = IUFindNumber(streamFrame, "WIDTH");
407 h = IUFindNumber(streamFrame, "HEIGHT");
408 }
409
410 if (w && h)
411 {
412 streamW = w->value;
413 streamH = h->value;
414 }
415 else
416 {
417 // Only use CCD dimensions if we are receiving raw stream and not stream of images (i.e. mjpeg..etc)
418 auto rawBP = getBLOB("CCD1");
419 if (rawBP)
420 {
421 int x = 0, y = 0, w = 0, h = 0;
422 int binx = 0, biny = 0;
423
424 primaryChip->getFrame(&x, &y, &w, &h);
425 primaryChip->getBinning(&binx, &biny);
426 streamW = w / binx;
427 streamH = h / biny;
428 }
429 }
430
431 emit updateVideoWindow(streamW, streamH, svp->sp[0].s == ISS_ON);
432 }
433
434 m_isStreamEnabled = (svp->sp[0].s == ISS_ON);
435 emit videoStreamToggled(m_isStreamEnabled);
436 }
437 else if (svp->isNameMatch("CCD_CAPTURE_FORMAT"))
438 {
439 m_CaptureFormats.clear();
440 for (int i = 0; i < svp->nsp; i++)
441 {
442 m_CaptureFormats << svp->sp[i].label;
443 if (svp->sp[i].s == ISS_ON)
444 m_CaptureFormatIndex = i;
445 }
446 }
447 else if (svp->isNameMatch("CCD_TRANSFER_FORMAT"))
448 {
449 ISwitch *format = IUFindOnSwitch(svp);
450 if (format)
451 m_EncodingFormat = format->label;
452 }
453 else if (svp->isNameMatch("RECORD_STREAM"))
454 {
455 ISwitch *recordOFF = IUFindSwitch(svp, "RECORD_OFF");
456
457 if (recordOFF && recordOFF->s == ISS_ON)
458 {
459 emit videoRecordToggled(false);
460 if (m_isStreamEnabled)
461 {
462 m_isStreamEnabled = false;
463 KSNotification::event(QLatin1String("IndiServerMessage"), i18n("Video Recording Stopped"), KSNotification::INDI);
464 }
465 }
466 else if (m_isStreamEnabled == false)
467 {
468 emit videoRecordToggled(true);
469 m_isStreamEnabled = true;
470 KSNotification::event(QLatin1String("IndiServerMessage"), i18n("Video Recording Started"), KSNotification::INDI);
471 }
472 }
473 else if (svp->isNameMatch("TELESCOPE_TYPE"))
474 {
475 ISwitch *format = IUFindSwitch(svp, "TELESCOPE_PRIMARY");
476 if (format && format->s == ISS_ON)
477 telescopeType = TELESCOPE_PRIMARY;
478 else
479 telescopeType = TELESCOPE_GUIDE;
480 }
481 else if (!strcmp(svp->name, "CCD_FAST_TOGGLE"))
482 {
483 m_FastExposureEnabled = IUFindOnSwitchIndex(svp) == 0;
484 }
485 else if (svp->isNameMatch("CONNECTION"))
486 {
487 auto dSwitch = svp->findWidgetByName("DISCONNECT");
488
489 if (dSwitch && dSwitch->getState() == ISS_ON)
490 {
491 emit videoStreamToggled(false);
492 emit closeVideoWindow();
493
494 // Clear the pointers on disconnect.
495 gainN = nullptr;
496 offsetN = nullptr;
497 primaryCCDBLOB = INDI::Property();
498 }
499 }
500}
501
502void Camera::processText(INDI::Property prop)
503{
504 auto tvp = prop.getText();
505 if (tvp->isNameMatch("CCD_FILE_PATH"))
506 {
507 auto filepath = tvp->findWidgetByName("FILE_PATH");
508 if (filepath)
509 emit newRemoteFile(QString(filepath->getText()));
510 }
511}
512
513void Camera::setWSBLOB(const QByteArray &message, const QString &extension)
514{
515 if (!primaryCCDBLOB)
516 return;
517
518 auto bvp = primaryCCDBLOB.getBLOB();
519 auto bp = bvp->at(0);
520
521 bp->setBlob(const_cast<char *>(message.data()));
522 bp->setSize(message.size());
523 bp->setFormat(extension.toLatin1().constData());
524 processBLOB(primaryCCDBLOB);
525
526 // Disassociate
527 bp->setBlob(nullptr);
528}
529
530void Camera::processStream(INDI::Property prop)
531{
532 if (m_isStreamEnabled == false)
533 return;
534
535 INumberVectorProperty *streamFrame = getNumber("CCD_STREAM_FRAME");
536 INumber *w = nullptr, *h = nullptr;
537
538 if (streamFrame)
539 {
540 w = IUFindNumber(streamFrame, "WIDTH");
541 h = IUFindNumber(streamFrame, "HEIGHT");
542 }
543
544 if (w && h)
545 {
546 streamW = w->value;
547 streamH = h->value;
548 }
549 else
550 {
551 int x = 0, y = 0, w = 0, h = 0;
552 int binx = 1, biny = 1;
553
554 primaryChip->getFrame(&x, &y, &w, &h);
555 primaryChip->getBinning(&binx, &biny);
556 streamW = w / binx;
557 streamH = h / biny;
558 }
559
560 emit showVideoFrame(prop, streamW, streamH);
561}
562
563void ISD::Camera::updateFileBuffer(INDI::Property prop, bool is_fits)
564{
565 if (is_fits)
566 {
567 // Check if the last write is still ongoing, and if so wait.
568 // It is using the fileWriteBuffer.
569 if (fileWriteThread.isRunning())
570 {
571 fileWriteThread.waitForFinished();
572 }
573 }
574
575 // Will write blob data in a separate thread, and can't depend on the blob
576 // memory, so copy it first.
577
578 auto bp = prop.getBLOB()->at(0);
579 // Check buffer size.
580 if (fileWriteBufferSize != bp->getBlobLen())
581 {
582 if (fileWriteBuffer != nullptr)
583 delete [] fileWriteBuffer;
584 fileWriteBufferSize = bp->getBlobLen();
585 fileWriteBuffer = new char[fileWriteBufferSize];
586 }
587
588 // Copy memory, and write file on a separate thread.
589 // Probably too late to return an error if the file couldn't write.
590 memcpy(fileWriteBuffer, bp->getBlob(), bp->getBlobLen());
591}
592
594{
595 // TODO: Not yet threading the writes for non-fits files.
596 // Would need to deal with the raw conversion, etc.
597 if (BType == BLOB_FITS)
598 {
599 // Check if the last write is still ongoing, and if so wait.
600 // It is using the fileWriteBuffer.
601 if (fileWriteThread.isRunning())
602 {
603 fileWriteThread.waitForFinished();
604 }
605
606 // Wait until the file is written before overwritting the filename.
607#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
608 fileWriteThread = QtConcurrent::run(&ISD::Camera::WriteImageFileInternal, this, filename, fileWriteBuffer,
609 fileWriteBufferSize);
610#else
611 fileWriteThread = QtConcurrent::run(this, &ISD::Camera::WriteImageFileInternal, filename, fileWriteBuffer,
612 fileWriteBufferSize);
613#endif
614 }
615 else if (!WriteImageFileInternal(filename, static_cast<char*>(fileWriteBuffer), fileWriteBufferSize))
616 return false;
617
618 return true;
619}
620
621bool Camera::processBLOB(INDI::Property prop)
622{
623 auto bvp = prop.getBLOB();
624 // Ignore write-only BLOBs since we only receive it for state-change
625 if (bvp->getPermission() == IP_WO || bvp->at(0)->getSize() == 0)
626 return false;
627
628 BType = BLOB_OTHER;
629
630 auto bp = bvp->at(0);
631
632 auto format = QString(bp->getFormat()).toLower();
633
634 // If stream, process it first
635 if (format.contains("stream"))
636 {
637 if (m_StreamingEnabled == false)
638 return true;
639
640 processStream(prop);
641 return true;
642 }
643
644 // Format without leading . (.jpg --> jpg)
645 QString shortFormat = format.mid(1);
646
647 // If it's not FITS or an image, don't process it.
648 if ((QImageReader::supportedImageFormats().contains(shortFormat.toLatin1())))
649 BType = BLOB_IMAGE;
650 else if (format.contains("fits"))
651 BType = BLOB_FITS;
652 else if (format.contains("xisf"))
653 BType = BLOB_XISF;
654 else if (RAWFormats.contains(shortFormat))
655 BType = BLOB_RAW;
656
657 if (BType == BLOB_OTHER)
658 return false;
659
660 CameraChip *targetChip = nullptr;
661
662 if (bvp->isNameMatch("CCD2"))
663 targetChip = guideChip.get();
664 else
665 {
666 targetChip = primaryChip.get();
667 qCDebug(KSTARS_INDI) << "Image received. Mode:" << getFITSModeStringString(targetChip->getCaptureMode()) << "Size:" <<
668 bp->getSize();
669 }
670
671 // Create temporary name if ANY of the following conditions are met:
672 // 1. file is preview or batch mode is not enabled
673 // 2. file type is not FITS_NORMAL (focus, guide..etc)
674 // create the file buffer only, saving the image file must be triggered from outside.
675 updateFileBuffer(prop, BType == BLOB_FITS);
676
677 // Don't spam, just one notification per 3 seconds
678 if (QDateTime::currentDateTime().secsTo(m_LastNotificationTS) <= -3)
679 {
680 KNotification::event(QLatin1String("FITSReceived"), i18n("Image file is received"));
681 m_LastNotificationTS = QDateTime::currentDateTime();
682 }
683
684 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(bp->getBlob()), bp->getSize());
685 QSharedPointer<FITSData> imageData;
686 imageData.reset(new FITSData(targetChip->getCaptureMode()), &QObject::deleteLater);
687 imageData->setExtension(shortFormat);
688
689 // JM 2024.12.25: Only load from buffer if we need the imageData.
690 // When neither FITS Viewer nor Summary view is used, and when the type is FITS_NORMAL in batch mode, then we save to disk directly
691 // so that we do not incur delays in loading from buffer that may delay the sequence unnecessairly.
692 if ((Options::useFITSViewer() || Options::useSummaryPreview() || targetChip->getCaptureMode() != FITS_NORMAL
693 || !targetChip->isBatchMode()) &&
694 !imageData->loadFromBuffer(buffer))
695 {
696 emit error(ERROR_LOAD);
697 return true;
698 }
699
700 // Add metadata
701 imageData->setProperty("device", getDeviceName());
702 imageData->setProperty("blobVector", prop.getName());
703 imageData->setProperty("blobElement", bp->getName());
704 imageData->setProperty("chip", targetChip->getType());
705
706 // Retain a copy
707 targetChip->setImageData(imageData);
708 emit propertyUpdated(prop);
709 emit newImage(imageData, QString(bp->getFormat()).toLower());
710
711 return true;
712}
713
714void Camera::StreamWindowHidden()
715{
716 if (isConnected())
717 {
718 // We can have more than one *_VIDEO_STREAM property active so disable them all
719 auto streamSP = getSwitch("CCD_VIDEO_STREAM");
720 if (streamSP)
721 {
722 streamSP->reset();
723 streamSP->at(0)->setState(ISS_OFF);
724 streamSP->at(1)->setState(ISS_ON);
725 streamSP->setState(IPS_IDLE);
726 sendNewProperty(streamSP);
727 }
728
729 streamSP = getSwitch("VIDEO_STREAM");
730 if (streamSP)
731 {
732 streamSP->reset();
733 streamSP->at(0)->setState(ISS_OFF);
734 streamSP->at(1)->setState(ISS_ON);
735 streamSP->setState(IPS_IDLE);
736 sendNewProperty(streamSP);
737 }
738
739 streamSP = getSwitch("AUX_VIDEO_STREAM");
740 if (streamSP)
741 {
742 streamSP->reset();
743 streamSP->at(0)->setState(ISS_OFF);
744 streamSP->at(1)->setState(ISS_ON);
745 streamSP->setState(IPS_IDLE);
746 sendNewProperty(streamSP);
747 }
748 }
749}
750
751bool Camera::hasGuideHead()
752{
753 return HasGuideHead;
754}
755
756bool Camera::hasCooler()
757{
758 return HasCooler;
759}
760
761bool Camera::hasCoolerControl()
762{
763 return HasCoolerControl;
764}
765
766bool Camera::setCoolerControl(bool enable)
767{
768 if (HasCoolerControl == false)
769 return false;
770
771 auto coolerSP = getSwitch("CCD_COOLER");
772
773 if (!coolerSP)
774 return false;
775
776 // Cooler ON/OFF
777 auto coolerON = coolerSP->findWidgetByName("COOLER_ON");
778 auto coolerOFF = coolerSP->findWidgetByName("COOLER_OFF");
779 if (!coolerON || !coolerOFF)
780 return false;
781
782 coolerON->setState(enable ? ISS_ON : ISS_OFF);
783 coolerOFF->setState(enable ? ISS_OFF : ISS_ON);
784 sendNewProperty(coolerSP);
785
786 return true;
787}
788
789CameraChip *Camera::getChip(CameraChip::ChipType cType)
790{
791 switch (cType)
792 {
793 case CameraChip::PRIMARY_CCD:
794 return primaryChip.get();
795
796 case CameraChip::GUIDE_CCD:
797 return guideChip.get();
798 }
799
800 return nullptr;
801}
802
803bool Camera::setRapidGuide(CameraChip *targetChip, bool enable)
804{
805 ISwitchVectorProperty *rapidSP = nullptr;
806 ISwitch *enableS = nullptr;
807
808 if (targetChip == primaryChip.get())
809 rapidSP = getSwitch("CCD_RAPID_GUIDE");
810 else
811 rapidSP = getSwitch("GUIDER_RAPID_GUIDE");
812
813 if (rapidSP == nullptr)
814 return false;
815
816 enableS = IUFindSwitch(rapidSP, "ENABLE");
817
818 if (enableS == nullptr)
819 return false;
820
821 // Already updated, return OK
822 if ((enable && enableS->s == ISS_ON) || (!enable && enableS->s == ISS_OFF))
823 return true;
824
825 IUResetSwitch(rapidSP);
826 rapidSP->sp[0].s = enable ? ISS_ON : ISS_OFF;
827 rapidSP->sp[1].s = enable ? ISS_OFF : ISS_ON;
828
829 sendNewProperty(rapidSP);
830
831 return true;
832}
833
834bool Camera::configureRapidGuide(CameraChip *targetChip, bool autoLoop, bool sendImage, bool showMarker)
835{
836 ISwitchVectorProperty *rapidSP = nullptr;
837 ISwitch *autoLoopS = nullptr, *sendImageS = nullptr, *showMarkerS = nullptr;
838
839 if (targetChip == primaryChip.get())
840 rapidSP = getSwitch("CCD_RAPID_GUIDE_SETUP");
841 else
842 rapidSP = getSwitch("GUIDER_RAPID_GUIDE_SETUP");
843
844 if (rapidSP == nullptr)
845 return false;
846
847 autoLoopS = IUFindSwitch(rapidSP, "AUTO_LOOP");
848 sendImageS = IUFindSwitch(rapidSP, "SEND_IMAGE");
849 showMarkerS = IUFindSwitch(rapidSP, "SHOW_MARKER");
850
851 if (!autoLoopS || !sendImageS || !showMarkerS)
852 return false;
853
854 // If everything is already set, let's return.
855 if (((autoLoop && autoLoopS->s == ISS_ON) || (!autoLoop && autoLoopS->s == ISS_OFF)) &&
856 ((sendImage && sendImageS->s == ISS_ON) || (!sendImage && sendImageS->s == ISS_OFF)) &&
857 ((showMarker && showMarkerS->s == ISS_ON) || (!showMarker && showMarkerS->s == ISS_OFF)))
858 return true;
859
860 autoLoopS->s = autoLoop ? ISS_ON : ISS_OFF;
861 sendImageS->s = sendImage ? ISS_ON : ISS_OFF;
862 showMarkerS->s = showMarker ? ISS_ON : ISS_OFF;
863
864 sendNewProperty(rapidSP);
865
866 return true;
867}
868
869void Camera::updateUploadSettings(const QString &uploadDirectory, const QString &uploadFile)
870{
871 ITextVectorProperty *uploadSettingsTP = nullptr;
872 IText *uploadT = nullptr;
873
874 uploadSettingsTP = getText("UPLOAD_SETTINGS");
875 if (uploadSettingsTP)
876 {
877 uploadT = IUFindText(uploadSettingsTP, "UPLOAD_DIR");
878 if (uploadT && uploadDirectory.isEmpty() == false)
879 {
880 auto posixDirectory = uploadDirectory;
881 // N.B. Need to convert any Windows directory separators / to Posix separators /
882 posixDirectory.replace(QDir::separator(), "/");
883 IUSaveText(uploadT, posixDirectory.toLatin1().constData());
884 }
885
886 uploadT = IUFindText(uploadSettingsTP, "UPLOAD_PREFIX");
887 if (uploadT)
888 IUSaveText(uploadT, uploadFile.toLatin1().constData());
889
890 sendNewProperty(uploadSettingsTP);
891 }
892}
893
894Camera::UploadMode Camera::getUploadMode()
895{
896 ISwitchVectorProperty *uploadModeSP = nullptr;
897
898 uploadModeSP = getSwitch("UPLOAD_MODE");
899
900 if (uploadModeSP == nullptr)
901 {
902 qWarning() << "No UPLOAD_MODE in CCD driver. Please update driver to INDI compliant CCD driver.";
903 return UPLOAD_CLIENT;
904 }
905
906 if (uploadModeSP)
907 {
908 ISwitch *modeS = nullptr;
909
910 modeS = IUFindSwitch(uploadModeSP, "UPLOAD_CLIENT");
911 if (modeS && modeS->s == ISS_ON)
912 return UPLOAD_CLIENT;
913 modeS = IUFindSwitch(uploadModeSP, "UPLOAD_LOCAL");
914 if (modeS && modeS->s == ISS_ON)
915 return UPLOAD_REMOTE;
916 modeS = IUFindSwitch(uploadModeSP, "UPLOAD_BOTH");
917 if (modeS && modeS->s == ISS_ON)
918 return UPLOAD_BOTH;
919 }
920
921 // Default
922 return UPLOAD_CLIENT;
923}
924
925bool Camera::setUploadMode(UploadMode mode)
926{
927 ISwitch *modeS = nullptr;
928
929 auto uploadModeSP = getSwitch("UPLOAD_MODE");
930
931 if (!uploadModeSP)
932 {
933 qWarning() << "No UPLOAD_MODE in CCD driver. Please update driver to INDI compliant CCD driver.";
934 return false;
935 }
936
937 switch (mode)
938 {
939 case UPLOAD_CLIENT:
940 modeS = uploadModeSP->findWidgetByName("UPLOAD_CLIENT");
941 if (!modeS)
942 return false;
943 if (modeS->s == ISS_ON)
944 return true;
945 break;
946
947 case UPLOAD_BOTH:
948 modeS = uploadModeSP->findWidgetByName("UPLOAD_BOTH");
949 if (!modeS)
950 return false;
951 if (modeS->s == ISS_ON)
952 return true;
953 break;
954
955 case UPLOAD_REMOTE:
956 modeS = uploadModeSP->findWidgetByName("UPLOAD_LOCAL");
957 if (!modeS)
958 return false;
959 if (modeS->s == ISS_ON)
960 return true;
961 break;
962 }
963
964 uploadModeSP->reset();
965 modeS->s = ISS_ON;
966
967 sendNewProperty(uploadModeSP);
968
969 return true;
970}
971
972bool Camera::getTemperature(double *value)
973{
974 if (HasCooler == false)
975 return false;
976
977 auto temperatureNP = getNumber("CCD_TEMPERATURE");
978
979 if (!temperatureNP)
980 return false;
981
982 *value = temperatureNP->at(0)->getValue();
983
984 return true;
985}
986
987bool Camera::setTemperature(double value)
988{
989 auto nvp = getNumber("CCD_TEMPERATURE");
990
991 if (!nvp)
992 return false;
993
994 auto np = nvp->findWidgetByName("CCD_TEMPERATURE_VALUE");
995
996 if (!np)
997 return false;
998
999 np->setValue(value);
1000
1001 sendNewProperty(nvp);
1002
1003 return true;
1004}
1005
1006bool Camera::setEncodingFormat(const QString &value)
1007{
1008 if (value.isEmpty() || value == m_EncodingFormat)
1009 return true;
1010
1011 auto svp = getSwitch("CCD_TRANSFER_FORMAT");
1012
1013 if (!svp)
1014 return false;
1015
1016 svp->reset();
1017 for (int i = 0; i < svp->nsp; i++)
1018 {
1019 if (svp->at(i)->getLabel() == value)
1020 {
1021 svp->at(i)->setState(ISS_ON);
1022 break;
1023 }
1024 }
1025
1026 m_EncodingFormat = value;
1027 sendNewProperty(svp);
1028 return true;
1029}
1030
1031bool Camera::setStreamEncoding(const QString &value)
1032{
1033 if (value.isEmpty() || value == m_StreamEncoding)
1034 return true;
1035
1036 auto svp = getSwitch("CCD_STREAM_ENCODER");
1037
1038 if (!svp)
1039 return false;
1040
1041 svp->reset();
1042 for (int i = 0; i < svp->nsp; i++)
1043 {
1044 if (svp->at(i)->getLabel() == value)
1045 {
1046 svp->at(i)->setState(ISS_ON);
1047 break;
1048 }
1049 }
1050
1051 m_StreamEncoding = value;
1052 sendNewProperty(svp);
1053 return true;
1054}
1055
1056bool Camera::setStreamRecording(const QString &value)
1057{
1058 if (value.isEmpty() || value == m_StreamRecording)
1059 return true;
1060
1061 auto svp = getSwitch("CCD_STREAM_RECORDER");
1062
1063 if (!svp)
1064 return false;
1065
1066 svp->reset();
1067 for (int i = 0; i < svp->nsp; i++)
1068 {
1069 if (svp->at(i)->getLabel() == value)
1070 {
1071 svp->at(i)->setState(ISS_ON);
1072 break;
1073 }
1074 }
1075
1076 m_StreamRecording = value;
1077 sendNewProperty(svp);
1078 return true;
1079}
1080
1081bool Camera::setTelescopeType(TelescopeType type)
1082{
1083 if (type == telescopeType)
1084 return true;
1085
1086 auto svp = getSwitch("TELESCOPE_TYPE");
1087
1088 if (!svp)
1089 return false;
1090
1091 auto typePrimary = svp->findWidgetByName("TELESCOPE_PRIMARY");
1092 auto typeGuide = svp->findWidgetByName("TELESCOPE_GUIDE");
1093
1094 if (!typePrimary || !typeGuide)
1095 return false;
1096
1097 telescopeType = type;
1098
1099 if ( (telescopeType == TELESCOPE_PRIMARY && typePrimary->getState() == ISS_OFF) ||
1100 (telescopeType == TELESCOPE_GUIDE && typeGuide->getState() == ISS_OFF))
1101 {
1102 typePrimary->setState(telescopeType == TELESCOPE_PRIMARY ? ISS_ON : ISS_OFF);
1103 typeGuide->setState(telescopeType == TELESCOPE_PRIMARY ? ISS_OFF : ISS_ON);
1104 sendNewProperty(svp);
1105 setConfig(SAVE_CONFIG);
1106 }
1107
1108 return true;
1109}
1110
1111bool Camera::setVideoStreamEnabled(bool enable)
1112{
1113 if (HasVideoStream == false)
1114 return false;
1115
1116 m_isStreamEnabled = enable;
1117
1118 auto svp = getSwitch("CCD_VIDEO_STREAM");
1119
1120 if (!svp)
1121 return false;
1122
1123 // If already on and enable is set or vice versa no need to change anything we return true
1124 if ((enable && svp->at(0)->getState() == ISS_ON) || (!enable && svp->at(1)->getState() == ISS_ON))
1125 return true;
1126
1127 svp->at(0)->setState(enable ? ISS_ON : ISS_OFF);
1128 svp->at(1)->setState(enable ? ISS_OFF : ISS_ON);
1129
1130 sendNewProperty(svp);
1131
1132 return true;
1133}
1134
1135bool Camera::resetStreamingFrame()
1136{
1137 auto frameProp = getNumber("CCD_STREAM_FRAME");
1138
1139 if (!frameProp)
1140 return false;
1141
1142 auto xarg = frameProp->findWidgetByName("X");
1143 auto yarg = frameProp->findWidgetByName("Y");
1144 auto warg = frameProp->findWidgetByName("WIDTH");
1145 auto harg = frameProp->findWidgetByName("HEIGHT");
1146
1147 if (xarg && yarg && warg && harg)
1148 {
1149 if (!std::fabs(xarg->getValue() - xarg->getMin()) &&
1150 !std::fabs(yarg->getValue() - yarg->getMin()) &&
1151 !std::fabs(warg->getValue() - warg->getMax()) &&
1152 !std::fabs(harg->getValue() - harg->getMax()))
1153 return false;
1154
1155 xarg->setValue(xarg->getMin());
1156 yarg->setValue(yarg->getMin());
1157 warg->setValue(warg->getMax());
1158 harg->setValue(harg->getMax());
1159
1160 sendNewProperty(frameProp);
1161 return true;
1162 }
1163
1164 return false;
1165}
1166
1167bool Camera::setStreamLimits(uint16_t maxBufferSize, uint16_t maxPreviewFPS)
1168{
1169 auto limitsProp = getNumber("LIMITS");
1170
1171 if (!limitsProp)
1172 return false;
1173
1174 auto bufferMax = limitsProp->findWidgetByName("LIMITS_BUFFER_MAX");
1175 auto previewFPS = limitsProp->findWidgetByName("LIMITS_PREVIEW_FPS");
1176
1177 if (bufferMax && previewFPS)
1178 {
1179 if(std::fabs(bufferMax->getValue() - maxBufferSize) > 0 || std::fabs(previewFPS->getValue() - maxPreviewFPS) > 0)
1180 {
1181 bufferMax->setValue(maxBufferSize);
1182 previewFPS->setValue(maxPreviewFPS);
1183 sendNewProperty(limitsProp);
1184 }
1185
1186 return true;
1187 }
1188
1189 return false;
1190}
1191
1192bool Camera::setStreamingFrame(int x, int y, int w, int h)
1193{
1194 auto frameProp = getNumber("CCD_STREAM_FRAME");
1195
1196 if (!frameProp)
1197 return false;
1198
1199 auto xarg = frameProp->findWidgetByName("X");
1200 auto yarg = frameProp->findWidgetByName("Y");
1201 auto warg = frameProp->findWidgetByName("WIDTH");
1202 auto harg = frameProp->findWidgetByName("HEIGHT");
1203
1204 if (xarg && yarg && warg && harg)
1205 {
1206 if (!std::fabs(xarg->getValue() - x) &&
1207 !std::fabs(yarg->getValue() - y) &&
1208 !std::fabs(warg->getValue() - w) &&
1209 !std::fabs(harg->getValue() - h))
1210 return true;
1211
1212 // N.B. We add offset since the X, Y are relative to whatever streaming frame is currently active
1213 xarg->value = qBound(xarg->getMin(), static_cast<double>(x) + xarg->getValue(), xarg->getMax());
1214 yarg->value = qBound(yarg->getMin(), static_cast<double>(y) + yarg->getValue(), yarg->getMax());
1215 warg->value = qBound(warg->getMin(), static_cast<double>(w), warg->getMax());
1216 harg->value = qBound(harg->getMin(), static_cast<double>(h), harg->getMax());
1217
1218 sendNewProperty(frameProp);
1219 return true;
1220 }
1221
1222 return false;
1223}
1224
1225bool Camera::isStreamingEnabled()
1226{
1227 return (HasVideoStream && m_isStreamEnabled);
1228}
1229
1230bool Camera::setSERNameDirectory(const QString &filename, const QString &directory)
1231{
1232 auto tvp = getText("RECORD_FILE");
1233
1234 if (!tvp)
1235 return false;
1236
1237 auto filenameT = tvp->findWidgetByName("RECORD_FILE_NAME");
1238 auto dirT = tvp->findWidgetByName("RECORD_FILE_DIR");
1239
1240 if (!filenameT || !dirT)
1241 return false;
1242
1243 filenameT->setText(filename.toLatin1().data());
1244 dirT->setText(directory.toLatin1().data());
1245
1246 sendNewProperty(tvp);
1247
1248 return true;
1249}
1250
1251bool Camera::getSERNameDirectory(QString &filename, QString &directory)
1252{
1253 auto tvp = getText("RECORD_FILE");
1254
1255 if (!tvp)
1256 return false;
1257
1258 auto filenameT = tvp->findWidgetByName("RECORD_FILE_NAME");
1259 auto dirT = tvp->findWidgetByName("RECORD_FILE_DIR");
1260
1261 if (!filenameT || !dirT)
1262 return false;
1263
1264 filename = QString(filenameT->getText());
1265 directory = QString(dirT->getText());
1266
1267 return true;
1268}
1269
1270bool Camera::startRecording()
1271{
1272 auto svp = getSwitch("RECORD_STREAM");
1273
1274 if (!svp)
1275 return false;
1276
1277 auto recordON = svp->findWidgetByName("RECORD_ON");
1278
1279 if (!recordON)
1280 return false;
1281
1282 if (recordON->getState() == ISS_ON)
1283 return true;
1284
1285 svp->reset();
1286 recordON->setState(ISS_ON);
1287
1288 sendNewProperty(svp);
1289
1290 return true;
1291}
1292
1293bool Camera::startDurationRecording(double duration)
1294{
1295 auto nvp = getNumber("RECORD_OPTIONS");
1296
1297 if (!nvp)
1298 return false;
1299
1300 auto durationN = nvp->findWidgetByName("RECORD_DURATION");
1301
1302 if (!durationN)
1303 return false;
1304
1305 auto svp = getSwitch("RECORD_STREAM");
1306
1307 if (!svp)
1308 return false;
1309
1310 auto recordON = svp->findWidgetByName("RECORD_DURATION_ON");
1311
1312 if (!recordON)
1313 return false;
1314
1315 if (recordON->getState() == ISS_ON)
1316 return true;
1317
1318 durationN->setValue(duration);
1319 sendNewProperty(nvp);
1320
1321 svp->reset();
1322 recordON->setState(ISS_ON);
1323
1324 sendNewProperty(svp);
1325
1326 return true;
1327}
1328
1329bool Camera::startFramesRecording(uint32_t frames)
1330{
1331 auto nvp = getNumber("RECORD_OPTIONS");
1332
1333 if (!nvp)
1334 return false;
1335
1336 auto frameN = nvp->findWidgetByName("RECORD_FRAME_TOTAL");
1337 auto svp = getSwitch("RECORD_STREAM");
1338
1339 if (!frameN || !svp)
1340 return false;
1341
1342 auto recordON = svp->findWidgetByName("RECORD_FRAME_ON");
1343
1344 if (!recordON)
1345 return false;
1346
1347 if (recordON->getState() == ISS_ON)
1348 return true;
1349
1350 frameN->setValue(frames);
1351 sendNewProperty(nvp);
1352
1353 svp->reset();
1354 recordON->setState(ISS_ON);
1355
1356 sendNewProperty(svp);
1357
1358 return true;
1359}
1360
1361bool Camera::stopRecording()
1362{
1363 auto svp = getSwitch("RECORD_STREAM");
1364
1365 if (!svp)
1366 return false;
1367
1368 auto recordOFF = svp->findWidgetByName("RECORD_OFF");
1369
1370 if (!recordOFF)
1371 return false;
1372
1373 // If already set
1374 if (recordOFF->getState() == ISS_ON)
1375 return true;
1376
1377 svp->reset();
1378 recordOFF->setState(ISS_ON);
1379
1380 sendNewProperty(svp);
1381
1382 return true;
1383}
1384
1385bool Camera::setFITSHeaders(const QList<FITSData::Record> &values)
1386{
1387 auto tvp = getText("FITS_HEADER");
1388
1389 // Only proceed if FITS header has 3 fields introduced with INDI v2.0.1
1390 if (!tvp || tvp->count() < 3)
1391 return false;
1392
1393 for (auto &record : values)
1394 {
1395 tvp->at(0)->setText(record.key.toLatin1().constData());
1396 tvp->at(1)->setText(record.value.toString().toLatin1().constData());
1397 tvp->at(2)->setText(record.comment.toLatin1().constData());
1398
1399 sendNewProperty(tvp);
1400 }
1401
1402 return true;
1403}
1404
1405bool Camera::setGain(double value)
1406{
1407 if (!gainN)
1408 return false;
1409
1410 gainN->value = value;
1411 sendNewProperty(gainN->nvp);
1412 return true;
1413}
1414
1415bool Camera::getGain(double *value)
1416{
1417 if (!gainN)
1418 return false;
1419
1420 *value = gainN->value;
1421
1422 return true;
1423}
1424
1425bool Camera::getGainMinMaxStep(double *min, double *max, double *step)
1426{
1427 if (!gainN)
1428 return false;
1429
1430 *min = gainN->min;
1431 *max = gainN->max;
1432 *step = gainN->step;
1433
1434 return true;
1435}
1436
1437bool Camera::setOffset(double value)
1438{
1439 if (!offsetN)
1440 return false;
1441
1442 offsetN->value = value;
1443 sendNewProperty(offsetN->nvp);
1444 return true;
1445}
1446
1447bool Camera::getOffset(double *value)
1448{
1449 if (!offsetN)
1450 return false;
1451
1452 *value = offsetN->value;
1453
1454 return true;
1455}
1456
1457bool Camera::getOffsetMinMaxStep(double *min, double *max, double *step)
1458{
1459 if (!offsetN)
1460 return false;
1461
1462 *min = offsetN->min;
1463 *max = offsetN->max;
1464 *step = offsetN->step;
1465
1466 return true;
1467}
1468
1469bool Camera::isBLOBEnabled()
1470{
1471 return (m_Parent->getClientManager()->isBLOBEnabled(getDeviceName(), "CCD1"));
1472}
1473
1474bool Camera::setBLOBEnabled(bool enable, const QString &prop)
1475{
1476 m_Parent->getClientManager()->setBLOBEnabled(enable, getDeviceName(), prop);
1477
1478 return true;
1479}
1480
1481bool Camera::setFastExposureEnabled(bool enable)
1482{
1483 // Set value immediately
1484 m_FastExposureEnabled = enable;
1485
1486 auto svp = getSwitch("CCD_FAST_TOGGLE");
1487
1488 if (!svp)
1489 return false;
1490
1491 svp->at(0)->setState(enable ? ISS_ON : ISS_OFF);
1492 svp->at(1)->setState(enable ? ISS_OFF : ISS_ON);
1493 sendNewProperty(svp);
1494
1495 return true;
1496}
1497
1498bool Camera::setCaptureFormat(const QString &format)
1499{
1500 auto svp = getSwitch("CCD_CAPTURE_FORMAT");
1501 if (!svp)
1502 return false;
1503
1504 for (auto &oneSwitch : *svp)
1505 oneSwitch.setState(oneSwitch.label == format ? ISS_ON : ISS_OFF);
1506
1507 sendNewProperty(svp);
1508 return true;
1509}
1510
1511bool Camera::setFastCount(uint32_t count)
1512{
1513 auto nvp = getNumber("CCD_FAST_COUNT");
1514
1515 if (!nvp)
1516 return false;
1517
1518 nvp->at(0)->setValue(count);
1519
1520 sendNewProperty(nvp);
1521
1522 return true;
1523}
1524
1525bool Camera::setStreamExposure(double duration)
1526{
1527 auto nvp = getNumber("STREAMING_EXPOSURE");
1528
1529 if (!nvp)
1530 return false;
1531
1532 nvp->at(0)->setValue(duration);
1533
1534 sendNewProperty(nvp);
1535
1536 return true;
1537}
1538
1539bool Camera::getStreamExposure(double *duration)
1540{
1541 auto nvp = getNumber("STREAMING_EXPOSURE");
1542
1543 if (!nvp)
1544 return false;
1545
1546 *duration = nvp->at(0)->getValue();
1547
1548 return true;
1549}
1550
1551bool Camera::isCoolerOn()
1552{
1553 auto svp = getSwitch("CCD_COOLER");
1554
1555 if (!svp)
1556 return false;
1557
1558 return (svp->at(0)->getState() == ISS_ON);
1559}
1560
1561bool Camera::getTemperatureRegulation(double &ramp, double &threshold)
1562{
1563 auto regulation = getProperty("CCD_TEMP_RAMP");
1564 if (!regulation.isValid())
1565 return false;
1566
1567 ramp = regulation.getNumber()->at(0)->getValue();
1568 threshold = regulation.getNumber()->at(1)->getValue();
1569 return true;
1570}
1571
1572bool Camera::setTemperatureRegulation(double ramp, double threshold)
1573{
1574 auto regulation = getProperty("CCD_TEMP_RAMP");
1575 if (!regulation.isValid())
1576 return false;
1577
1578 regulation.getNumber()->at(0)->setValue(ramp);
1579 regulation.getNumber()->at(1)->setValue(threshold);
1580 sendNewProperty(regulation.getNumber());
1581 return true;
1582}
1583
1584bool Camera::setScopeInfo(double focalLength, double aperture)
1585{
1586 auto scopeInfo = getProperty("SCOPE_INFO");
1587 if (!scopeInfo.isValid())
1588 return false;
1589
1590 auto nvp = scopeInfo.getNumber();
1591 nvp->at(0)->setValue(focalLength);
1592 nvp->at(1)->setValue(aperture);
1593 sendNewProperty(nvp);
1594 return true;
1595}
1596
1597// Internal function to write an image blob to disk.
1598bool Camera::WriteImageFileInternal(const QString &filename, char *buffer, const size_t size)
1599{
1600 QFile file(filename);
1601 if (!file.open(QIODevice::WriteOnly))
1602 {
1603 qCCritical(KSTARS_INDI) << "ISD:CCD Error: Unable to open write file: " <<
1604 filename;
1605 return false;
1606 }
1607 int n = 0;
1608 QDataStream out(&file);
1609 bool ok = true;
1610 for (size_t nr = 0; nr < size; nr += n)
1611 {
1612 n = out.writeRawData(buffer + nr, size - nr);
1613 if (n < 0)
1614 {
1615 ok = false;
1616 break;
1617 }
1618 }
1619 ok = file.flush() && ok;
1620 file.close();
1621 file.setPermissions(QFileDevice::ReadUser |
1625 return ok;
1626}
1627
1628QString Camera::getCaptureFormat() const
1629{
1630 if (m_CaptureFormatIndex < 0 || m_CaptureFormats.isEmpty() || m_CaptureFormatIndex >= m_CaptureFormats.size())
1631 return QLatin1String("NA");
1632
1633 return m_CaptureFormats[m_CaptureFormatIndex];
1634}
1635}
CameraChip class controls a particular chip in camera.
@ ERROR_LOAD
Saving to disk error.
Definition indicamera.h:67
bool saveCurrentImage(QString &filename)
saveCurrentImage save the image that is currently in the image data buffer
The ConcreteDevice class.
void sendNewProperty(INDI::Property prop)
Send new property command to server.
INDI::PropertyView< IText > * getText(const QString &name) const
INDI::PropertyView< ISwitch > * getSwitch(const QString &name) const
GenericDevice is the Generic Device for INDI devices.
Definition indistd.h:117
static KNotification * event(const QString &eventId, const QString &text=QString(), const QPixmap &pixmap=QPixmap(), const NotificationFlags &flags=CloseOnTimeout, const QString &componentName=QString())
QString i18n(const char *text, const TYPE &arg...)
ISD is a collection of INDI Standard Devices.
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QString name(StandardAction id)
QString label(StandardShortcut id)
const char * constData() const const
char * data()
QByteArray fromRawData(const char *data, qsizetype size)
qsizetype size() const const
QDateTime currentDateTime()
QChar separator()
QList< QByteArray > supportedImageFormats()
qsizetype count() const const
void deleteLater()
QString arg(Args &&... args) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
QByteArray toLatin1() const const
QString toLower() const const
UniqueConnection
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:53:02 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.