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

KDE's Doxygen guidelines are available online.