2 Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com)
4 This apppication is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 2004-01-18: This class handles INDI Standard properties.
14 #include "indielement.h"
15 #include "indiproperty.h"
16 #include "indigroup.h"
17 #include "indidevice.h"
18 #include "indidriver.h"
20 #include "kstarsdata.h"
22 #include "skyobject.h"
24 #include "devicemanager.h"
25 #include "timedialog.h"
27 #include "fitsviewer.h"
29 #include <sys/socket.h>
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
39 #include <qeventloop.h>
40 #include <qsocketnotifier.h>
44 #include <kpushbutton.h>
45 #include <klineedit.h>
46 #include <kstatusbar.h>
47 #include <kmessagebox.h>
48 #include <kapplication.h>
49 #include <kprogress.h>
51 #include <kdirlister.h>
55 #define STD_BUFFER_SIZ 1024000
56 #define FRAME_ILEN 1024
58 INDIStdDevice::INDIStdDevice(INDI_D
*associatedDevice
, KStars
* kswPtr
)
61 dp
= associatedDevice
;
69 streamWindow
= new StreamWG(this, ksw
);
70 devTimer
= new QTimer(this);
71 seqLister
= new KDirLister();
73 connect( devTimer
, SIGNAL(timeout()), this, SLOT(timerDone()) );
74 connect( seqLister
, SIGNAL(newItems (const KFileItemList
& )), this, SLOT(checkSeqBoundary(const KFileItemList
&)));
76 downloadDialog
= new KProgressDialog(NULL
, 0, i18n("INDI"), i18n("Downloading Data..."));
77 downloadDialog
->cancel();
82 INDIStdDevice::~INDIStdDevice()
84 streamWindow
->enableStream(false);
85 streamWindow
->close();
89 void INDIStdDevice::handleBLOB(unsigned char *buffer
, int bufferSize
, int dataType
)
92 if (dataType
== DATA_STREAM
)
94 if (!streamWindow
->processStream
)
99 streamWindow
->streamFrame
->newFrame( buffer
, bufferSize
, streamWindow
->streamWidth
, streamWindow
->streamHeight
);
101 else if (dataType
== DATA_FITS
|| dataType
== DATA_OTHER
)
106 QString currentDir
= Options::fitsSaveDirectory();
108 streamWindow
->close();
110 if (dataType
== DATA_FITS
&& !batchMode
&& Options::indiFITSDisplay())
112 strcpy(filename
, "/tmp/fitsXXXXXX");
113 if ((fd
= mkstemp(filename
)) < 0)
115 KMessageBox::error(NULL
, "Error making temporary filename.");
128 if (currentDir
[currentDir
.length() -1] == '/')
129 currentDir
.truncate(currentDir
.length() - 1);
131 strncpy(filename
, currentDir
.ascii(), currentDir
.length());
132 filename
[currentDir
.length()] = '\0';
134 if (dataType
== DATA_FITS
)
136 char tempFileStr
[256];
137 strncpy(tempFileStr
, filename
, 256);
139 if ( batchMode
&& !ISOMode
)
140 snprintf(filename
, sizeof(filename
), "%s/%s_%02d.fits", tempFileStr
, seqPrefix
.ascii(), seqCount
);
141 else if (!batchMode
&& !Options::indiFITSDisplay())
143 strftime (ts
, sizeof(ts
), "%Y-%m-%dT%H:%M:%S", tp
);
144 snprintf(filename
, sizeof(filename
), "%s/file_%s.fits", tempFileStr
, ts
);
148 strftime (ts
, sizeof(ts
), "%Y-%m-%dT%H:%M:%S", tp
);
149 snprintf(filename
, sizeof(filename
), "%s/%s_%02d_%s.fits", tempFileStr
, seqPrefix
.ascii(), seqCount
, ts
);
156 strftime (ts
, sizeof(ts
), "/file-%Y-%m-%dT%H:%M:%S.", tp
);
157 strncat(filename
, ts
, sizeof(ts
));
158 strncat(filename
, dataExt
.ascii(), 3);
162 fitsTempFile
= fopen(filename
, "w");
164 if (fitsTempFile
== NULL
) return;
166 for (nr
=0; nr
< (int) bufferSize
; nr
+= n
)
167 n
= fwrite( ((unsigned char *) buffer
) + nr
, 1, bufferSize
- nr
, fitsTempFile
);
169 fclose(fitsTempFile
);
171 // We're done if we have DATA_OTHER
172 if (dataType
== DATA_OTHER
)
174 ksw
->statusBar()->changeItem( i18n("Data file saved to %1").arg(filename
), 0);
177 else if (dataType
== DATA_FITS
&& (batchMode
|| !Options::indiFITSDisplay()))
179 ksw
->statusBar()->changeItem( i18n("FITS file saved to %1").arg(filename
), 0);
180 emit
FITSReceived(dp
->label
);
184 KURL
fileURL(filename
);
186 FITSViewer
* fv
= new FITSViewer(&fileURL
, ksw
);
193 void INDIStdDevice::setTextValue(INDI_P
*pp
)
197 int d
, m
, y
, min
, sec
, hour
;
200 KStarsDateTime indiDateTime
;
204 case EQUATORIAL_COORD
:
205 case EQUATORIAL_EOD_COORD
:
206 //ksw->map()->forceUpdateNow();
207 ksw
->map()->update();
212 if ( Options::indiAutoTime() )
215 // Update KStars time once we receive update from INDI
216 el
= pp
->findElement("UTC");
219 sscanf(el
->text
.ascii(), "%d%*[^0-9]%d%*[^0-9]%dT%d%*[^0-9]%d%*[^0-9]%d", &y
, &m
, &d
, &hour
, &min
, &sec
);
220 indiDate
.setYMD(y
, m
, d
);
221 indiTime
.setHMS(hour
, min
, sec
);
222 indiDateTime
.setDate(indiDate
);
223 indiDateTime
.setTime(indiTime
);
225 ksw
->data()->changeDateTime(indiDateTime
);
226 ksw
->data()->syncLST();
231 if ( Options::indiAutoTime())
235 case GEOGRAPHIC_COORD
:
236 if ( Options::indiAutoGeo() )
240 case EXPOSE_DURATION
:
241 if (pp
->state
== PS_IDLE
|| pp
->state
== PS_OK
)
242 pp
->set_w
->setText(i18n("Start"));
246 el
= pp
->findElement("WIDTH");
248 wd
= (int) el
->value
;
249 el
= pp
->findElement("HEIGHT");
251 ht
= (int) el
->value
;
253 streamWindow
->setSize(wd
, ht
);
254 //streamWindow->allocateStreamBuffer();
264 void INDIStdDevice::setLabelState(INDI_P
*pp
)
269 INDIDriver
*drivers
= ksw
->getINDIDriver();
275 lp
= pp
->findElement("CONNECT");
278 if (lp
->state
== PS_ON
)
283 imgProp
= dp
->findProp("EXPOSE_DURATION");
286 tmpAction
= ksw
->actionCollection()->action("capture_sequence");
288 kdDebug() << "Warning: capture_sequence action not found" << endl
;
290 tmpAction
->setEnabled(true);
297 //sNotifier->disconnect();
298 //dp->parentMgr->sNotifier->disconnect();
299 streamWindow
->enableStream(false);
300 streamWindow
->close();
305 drivers
->updateMenuActions();
306 ksw
->map()->forceUpdateNow();
312 lp
= pp
->findElement("ON");
314 if (lp
->state
== PS_ON
)
315 streamWindow
->enableStream(true);
317 streamWindow
->enableStream(false);
326 void INDIStdDevice::streamDisabled()
331 //pp = dp->findProp("CONNECTION");
333 //if (pp->state == PS_OFF) return;
335 pp
= dp
->findProp("VIDEO_STREAM");
338 el
= pp
->findElement("OFF");
341 if (el
->state
== PS_ON
)
349 void INDIStdDevice::updateSequencePrefix(QString newPrefix
)
351 seqPrefix
= newPrefix
;
353 seqLister
->setNameFilter(QString("%1_*.fits").arg(seqPrefix
));
359 seqLister
->openURL(Options::fitsSaveDirectory());
361 checkSeqBoundary(seqLister
->items());
365 void INDIStdDevice::checkSeqBoundary(const KFileItemList
& items
)
369 char *tempPrefix
= new char[64];
371 // No need to check when in ISO mode
375 for ( KFileItemListIterator
it( items
) ; it
.current() ; ++it
)
377 tempName
= it
.current()->name();
379 // find the prefix first
380 if (tempName
.find(seqPrefix
) == -1)
383 strncpy(tempPrefix
, tempName
.ascii(), 64);
384 tempPrefix
[63] = '\0';
386 char * t
= tempPrefix
;
389 while (*t
) { if (isdigit(*t
)) break; t
++; }
392 newFileIndex
= strtol(t
, NULL
, 10);
394 if (newFileIndex
>= seqCount
)
395 seqCount
= newFileIndex
+ 1;
402 void INDIStdDevice::updateTime()
407 pp
= dp
->findProp("TIME");
410 lp
= pp
->findElement("UTC");
414 QTime
newTime( ksw
->data()->ut().time());
415 ExtDate
newDate( ksw
->data()->ut().date());
417 lp
->write_w
->setText(QString("%1-%2-%3T%4:%5:%6").arg(newDate
.year()).arg(newDate
.month())
418 .arg(newDate
.day()).arg(newTime
.hour())
419 .arg(newTime
.minute()).arg(newTime
.second()));
422 pp
= dp
->findProp("SDTIME");
424 lp
= pp
->findElement("LST");
427 lp
->write_w
->setText(ksw
->LST()->toHMSString());
431 void INDIStdDevice::updateLocation()
434 INDI_E
* latEle
, * longEle
;
435 GeoLocation
*geo
= ksw
->geo();
437 pp
= dp
->findProp("GEOGRAPHIC_COORD");
440 dms
tempLong (geo
->lng()->degree(), geo
->lng()->arcmin(), geo
->lng()->arcsec());
441 dms
fullCir(360,0,0);
443 if (tempLong
.degree() < 0)
444 tempLong
.setD ( fullCir
.Degrees() + tempLong
.Degrees());
446 latEle
= pp
->findElement("LAT");
448 longEle
= pp
->findElement("LONG");
449 if (!longEle
) return;
451 longEle
->write_w
->setText(QString("%1:%2:%3").arg(tempLong
.degree()).arg(tempLong
.arcmin()).arg(tempLong
.arcsec()));
452 latEle
->write_w
->setText(QString("%1:%2:%3").arg(geo
->lat()->degree()).arg(geo
->lat()->arcmin()).arg(geo
->lat()->arcsec()));
458 void INDIStdDevice::registerProperty(INDI_P
*pp
)
461 INDIDriver
*drivers
= ksw
->getINDIDriver();
467 portEle
= pp
->findElement("PORT");
468 if (!portEle
) return;
472 for (unsigned int i
=0; i
< drivers
->devices
.size(); i
++)
474 if (drivers
->devices
[i
]->mgrID
== dp
->parentMgr
->mgrID
)
476 if (drivers
->devices
[i
]->deviceType
== KSTARS_TELESCOPE
)
478 portEle
->read_w
->setText( Options::indiTelescopePort() );
479 portEle
->write_w
->setText( Options::indiTelescopePort() );
480 portEle
->text
= Options::indiTelescopePort();
483 else if (drivers
->devices
[i
]->deviceType
== KSTARS_VIDEO
)
485 portEle
->read_w
->setText( Options::indiVideoPort() );
486 portEle
->write_w
->setText( Options::indiVideoPort() );
487 portEle
->text
= Options::indiVideoPort();
499 void INDIStdDevice::initDeviceOptions()
506 if ( Options::indiAutoTime() )
508 prop
= dp
->findProp("TIME");
516 if ( Options::indiAutoGeo() )
518 prop
= dp
->findProp("GEOGRAPHIC_COORD");
526 if ( Options::indiMessages() )
527 ksw
->statusBar()->changeItem( i18n("%1 is online.").arg(dp
->name
), 0);
529 ksw
->map()->forceUpdateNow();
532 void INDIStdDevice::handleDevCounter()
535 if (initDevCounter
<= 0)
540 if ( initDevCounter
== 0 && Options::indiMessages() )
541 ksw
->statusBar()->changeItem( i18n("%1 is online and ready.").arg(dp
->name
), 0);
545 bool INDIStdDevice::handleNonSidereal(SkyObject
*o
)
552 kdDebug() << "Object of type " << o
->typeName() << endl
;
553 //TODO Meade claims that the library access is available to
554 // all telescopes, which is unture. Only classic meade support
555 // that. They claim that library funcion will be available to all
556 // in "later" firmware revisions for the autostar and GPS.
557 // As a temprory solution, I'm going to explicity check for the
558 // device name which ideally I'm not supposed to do since all properties
559 // should be defined from the INDI driver, but since the INDI autostar
560 // and gps define the library functions (based on Meade's future claims)
561 // I will check the device name until Meade's respondes to my query.
564 // Only Meade Classic will offer an explicit SOLAR_SYSTEM property. If such a property exists
565 // then we take advantage of it. Otherwise, we send RA/DEC to the telescope and start a timer
566 // based on the object type. Objects with high proper motions will require faster updates.
567 // handle Non Sideral is ONLY called when tracking an object, not slewing.
570 INDI_P
*prop
= dp
->findProp(QString("SOLAR_SYSTEM"));
571 INDI_P
*setMode
= dp
->findProp(QString("ON_COORD_SET"));
573 // If the device support it
576 for (unsigned int i
=0; i
< setMode
->el
.count(); i
++)
577 if (setMode
->el
.at(i
)->name
== "TRACK")
578 { trackIndex
= i
; break; }
580 kdDebug() << "Device supports SOLAR_SYSTEM property" << endl
;
582 for (unsigned int i
=0; i
< prop
->el
.count(); i
++)
583 if (o
->name().lower() == prop
->el
.at(i
)->label
.lower())
586 setMode
->newSwitch(trackIndex
);
591 kdDebug() << "Device doesn't support SOLAR_SYSTEM property, issuing a timer" << endl
;
592 kdDebug() << "Starting timer for object of type " << o
->typeName() << endl
;
595 switch (currentObject
->type())
599 kdDebug() << "Initiating pulse tracking for " << currentObject
->name() << endl
;
600 devTimer
->start(INDI_PULSE_TRACKING
);
605 kdDebug() << "Initiating pulse tracking for " << currentObject
->name() << endl
;
606 devTimer
->start(INDI_PULSE_TRACKING
);
616 void INDIStdDevice::timerDone()
619 INDI_E
*RAEle
, *DecEle
;
621 bool useJ2000
= false;
629 prop
= dp
->findProp("ON_COORD_SET");
630 if (prop
== NULL
|| !currentObject
)
633 el
= prop
->findElement("TRACK");
636 if (el
->state
!= PS_ON
)
642 prop
= dp
->findProp("EQUATORIAL_EOD_COORD");
646 prop
= dp
->findProp("EQUATORIAL_COORD");
647 if (prop
) useJ2000
= true;
650 if (prop
== NULL
|| !currentObject
)
653 // wait until slew is done
654 if (prop
->state
== PS_BUSY
)
657 kdDebug() << "Timer called, starting processing" << endl
;
659 SkyPoint
sp(currentObject
->ra(), currentObject
->dec());
661 kdDebug() << "RA: " << currentObject
->ra()->toHMSString() << " - DEC: " << currentObject
->dec()->toDMSString() << endl
;
662 kdDebug() << "Az: " << currentObject
->az()->toHMSString() << " - Alt " << currentObject
->alt()->toDMSString() << endl
;
665 sp
.apparentCoord( ksw
->data()->ut().djd() , (long double) J2000
);
667 // We need to get from JNow (Skypoint) to J2000
668 // The ra0() of a skyPoint is the same as its JNow ra() without this process
670 // Use J2000 coordinate as required by INDI
671 RAEle
= prop
->findElement("RA");
673 DecEle
= prop
->findElement("DEC");
676 RAEle
->write_w
->setText(QString("%1:%2:%3").arg(sp
.ra()->hour())
677 .arg(sp
.ra()->minute())
678 .arg(sp
.ra()->second()));
679 DecEle
->write_w
->setText(QString("%1:%2:%3").arg(sp
.dec()->degree())
680 .arg(sp
.dec()->arcmin())
681 .arg(sp
.dec()->arcsec()));
686 INDIStdProperty::INDIStdProperty(INDI_P
*associatedProperty
, KStars
* kswPtr
, INDIStdDevice
*stdDevPtr
)
688 pp
= associatedProperty
;
693 INDIStdProperty::~INDIStdProperty()
698 void INDIStdProperty::newText()
701 INDIDriver
*drivers
= ksw
->getINDIDriver();
705 /* Set expose duration button to 'cancel' when busy */
706 case EXPOSE_DURATION
:
707 pp
->set_w
->setText(i18n("Cancel"));
710 /* Save Port name in KStars options */
712 lp
= pp
->findElement("PORT");
716 for (unsigned int i
=0; i
< drivers
->devices
.size(); i
++)
718 if (drivers
->devices
[i
]->mgrID
== stdDev
->dp
->parentMgr
->mgrID
)
720 if (drivers
->devices
[i
]->deviceType
== KSTARS_TELESCOPE
)
722 Options::setIndiTelescopePort( lp
->text
);
723 kdDebug() << "Setting telescope port " << lp
->text
<< endl
;
725 else if (drivers
->devices
[i
]->deviceType
== KSTARS_VIDEO
)
727 Options::setIndiVideoPort( lp
->text
);
728 kdDebug() << "Setting video port " << lp
->text
<< endl
;
740 bool INDIStdProperty::convertSwitch(int switchIndex
, INDI_E
*lp
)
743 INDI_E
*RAEle
, *DecEle
;
746 bool useJ2000
= false;
750 /* Handle Slew/Track/Sync */
752 // #1 set current object to NULL
753 stdDev
->currentObject
= NULL
;
754 // #2 Deactivate timer if present
755 if (stdDev
->devTimer
->isActive())
756 stdDev
->devTimer
->stop();
758 prop
= pp
->pg
->dp
->findProp("EQUATORIAL_EOD_COORD");
761 prop
= pp
->pg
->dp
->findProp("EQUATORIAL_COORD");
768 RAEle
= prop
->findElement("RA");
769 if (!RAEle
) return false;
770 DecEle
= prop
->findElement("DEC");
771 if (!DecEle
) return false;
773 // Track is similar to slew, except that for non-sidereal objects
774 // it tracks the objects automatically via a timer.
775 if ((lp
->name
== "TRACK"))
776 if (stdDev
->handleNonSidereal(ksw
->map()->clickedObject()))
779 kdDebug() << "\n******** The point BEFORE it was precessed ********" << endl
;
780 kdDebug() << "RA : " << ksw
->map()->clickedPoint()->ra()->toHMSString() << " - DEC : " << ksw
->map()->clickedPoint()->dec()->toDMSString() << endl
;
783 // We need to get from JNow (Skypoint) to J2000
784 // The ra0() of a skyPoint is the same as its JNow ra() without this process
785 if (stdDev
->currentObject
)
786 sp
.set (stdDev
->currentObject
->ra(), stdDev
->currentObject
->dec());
788 sp
.set (ksw
->map()->clickedPoint()->ra(), ksw
->map()->clickedPoint()->dec());
791 sp
.apparentCoord(ksw
->data()->ut().djd(), (long double) J2000
);
793 // Use J2000 coordinate as required by INDI
794 RAEle
->write_w
->setText(QString("%1:%2:%3").arg(sp
.ra()->hour()).arg(sp
.ra()->minute()).arg(sp
.ra()->second()));
795 DecEle
->write_w
->setText(QString("%1:%2:%3").arg(sp
.dec()->degree()).arg(sp
.dec()->arcmin()).arg(sp
.dec()->arcsec()));
799 kdDebug() << "\n******** The point AFTER it was precessed ********" << endl
;
800 kdDebug() << "RA : " << sp
.ra()->toHMSString() << " - DEC : " << sp
.dec()->toDMSString() << endl
;
804 //sp.apparentCoord((long double) J2000, ksw->data()->ut().djd());
805 //kdDebug() << "\n******** The point AFTER it was precessed AGAIN to JNOW ********" << endl;
806 //kdDebug() << "RA : " << sp.ra()->toHMSString() << " - DEC : " << sp.dec()->toDMSString() << endl;
808 pp
->newSwitch(switchIndex
);
816 kdDebug() << "Stopping timer." << endl
;
817 stdDev
->devTimer
->stop();
818 pp
->newSwitch(switchIndex
);
823 pp
->newSwitch(switchIndex
);
835 // Return true if the complete operation is done here, or false if the operation is to be completed in the properties newSwitch()
836 bool INDIStdProperty::newSwitch(int id
, INDI_E
* el
)
845 stdDev
->streamDisabled();
848 prop
= pp
->pg
->dp
->findProp("DEVICE_PORT");
856 //TODO add text in the status bar "Slew aborted."
857 stdDev
->devTimer
->stop();
867 void INDIStdProperty::newTime()
872 timeEle
= pp
->findElement("UTC");
873 if (!timeEle
) return;
875 TimeDialog
timedialog ( ksw
->data()->ut(), ksw
);
877 if ( timedialog
.exec() == QDialog::Accepted
)
879 QTime
newTime( timedialog
.selectedTime() );
880 ExtDate
newDate( timedialog
.selectedDate() );
882 timeEle
->write_w
->setText(QString("%1-%2-%3T%4:%5:%6")
883 .arg(newDate
.year()).arg(newDate
.month())
884 .arg(newDate
.day()).arg(newTime
.hour())
885 .arg(newTime
.minute()).arg(newTime
.second()));
890 SDProp
= pp
->pg
->dp
->findProp("SDTIME");
892 timeEle
= SDProp
->findElement("LST");
893 if (!timeEle
) return;
895 timeEle
->write_w
->setText(ksw
->LST()->toHMSString());
899 #include "indistd.moc"