1 /*
2  *  cam_sbig.cpp
3  *  PHD Guiding
4  *
5  *  Created by Craig Stark.
6  *  Copyright (c) 2007-2010 Craig Stark.
7  *  All rights reserved.
8  *
9  *  This source code is distributed under the following "BSD" license
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions are met:
12  *    Redistributions of source code must retain the above copyright notice,
13  *     this list of conditions and the following disclaimer.
14  *    Redistributions in binary form must reproduce the above copyright notice,
15  *     this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *    Neither the name of Craig Stark, Stark Labs nor the names of its
18  *     contributors may be used to endorse or promote products derived from
19  *     this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  *  POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 
35 #include "phd.h"
36 
37 #if defined(SBIG)
38 
39 #include "cam_sbig.h"
40 #include "camera.h"
41 #include "image_math.h"
42 
43 #include <time.h>
44 #include <wx/textdlg.h>
45 
46 #if defined (__APPLE__)
47 #include <SBIGUDrv/sbigudrv.h>
48 #elif defined(__LINUX__)
49 #include <sbigudrv.h>
50 #else
51 #include "cameras/Sbigudrv.h"
52 #endif
53 
54 class CameraSBIG : public GuideCamera
55 {
56     bool m_useTrackingCCD;
57     bool m_driverLoaded;
58     wxSize m_imageSize[2]; // 0=>bin1, 1=>bin2
59     double m_devicePixelSize;
60     bool IsColor;
61 
62 public:
63     CameraSBIG();
64     ~CameraSBIG();
65 
CanSelectCamera() const66     bool CanSelectCamera() const override { return true; }
67     bool HandleSelectCameraButtonClick(wxCommandEvent& evt) override;
68     bool Capture(int duration, usImage& img, int options, const wxRect& subframe) override;
69     bool Connect(const wxString& camId) override;
70     bool Disconnect() override;
71     void InitCapture() override;
72     bool ST4PulseGuideScope(int direction, int duration) override;
ST4HasNonGuiMove()73     bool ST4HasNonGuiMove() override { return true; }
HasNonGuiCapture()74     bool HasNonGuiCapture() override { return true; }
75     wxByte BitsPerPixel() override;
76     bool GetDevicePixelSize(double *devPixelSize) override;
77 
78 private:
79     bool LoadDriver();
80 };
81 
bcd2long(unsigned long bcd)82 static unsigned long bcd2long(unsigned long bcd)
83 {
84     int pos = sizeof(bcd) * 8;
85     int digit;
86     unsigned long val = 0;
87 
88     do {
89         pos -= 4;
90         digit = (bcd >> pos) & 0xf;
91         val = val * 10 + digit;
92     } while (pos > 0);
93 
94     return val;
95 }
96 
CameraSBIG()97 CameraSBIG::CameraSBIG()
98     : m_driverLoaded(false)
99 {
100     Connected = false;
101     Name = _T("SBIG");
102     //FullSize = wxSize(1280,1024);
103     //HasGainControl = true;
104     m_hasGuideOutput = true;
105     m_useTrackingCCD = false;
106     HasShutter = true;
107     HasSubframes = true;
108     IsColor = false;
109 }
110 
~CameraSBIG()111 CameraSBIG::~CameraSBIG()
112 {
113     if (m_driverLoaded)
114         SBIGUnivDrvCommand(CC_CLOSE_DRIVER, NULL, NULL);
115 }
116 
BitsPerPixel()117 wxByte CameraSBIG::BitsPerPixel()
118 {
119     return 16;
120 }
121 
_LoadDriver()122 static bool _LoadDriver()
123 {
124     short err;
125 
126 #if defined (__WINDOWS__)
127     __try {
128         err = SBIGUnivDrvCommand(CC_OPEN_DRIVER, NULL, NULL);
129     }
130     __except (EXCEPTION_EXECUTE_HANDLER) {
131         err = CE_DRIVER_NOT_FOUND;
132     }
133 #else
134     err = SBIGUnivDrvCommand(CC_OPEN_DRIVER, NULL, NULL);
135 #endif
136 
137     return err == CE_NO_ERROR;
138 }
139 
LoadDriver()140 bool CameraSBIG::LoadDriver()
141 {
142     if (m_driverLoaded)
143         return true;
144 
145     bool ok = _LoadDriver();
146     if (ok)
147         m_driverLoaded = true;
148     else
149         wxMessageBox(_("Error loading SBIG driver and/or DLL"));
150 
151     return ok;
152 }
153 
SelectInterfaceAndDevice()154 static bool SelectInterfaceAndDevice()
155 {
156     // select which cam interface
157     wxArrayString interf;
158 
159     interf.Add("USB");
160     interf.Add("Ethernet");
161 #if defined (__WINDOWS__)
162     interf.Add("LPT 0x378");
163     interf.Add("LPT 0x278");
164     interf.Add("LPT 0x3BC");
165 #else
166     interf.Add("USB1 direct");
167     interf.Add("USB2 direct");
168     interf.Add("USB3 direct");
169 #endif
170 
171     int resp = pConfig->Profile.GetInt("/camera/sbig/interface", 0);
172     resp = wxGetSingleChoiceIndex(_("Select interface"), _("Interface"), interf,
173         NULL, wxDefaultCoord, wxDefaultCoord, true, wxCHOICE_WIDTH, wxCHOICE_HEIGHT,
174         resp);
175 
176     if (resp == -1)
177     {
178         // user hit cancel
179         return true;
180     }
181 
182     pConfig->Profile.SetInt("/camera/sbig/interface", resp);
183 
184     OpenDeviceParams odp = { 0 };
185 
186     short err;
187 
188     switch (resp) {
189     case 0:
190         odp.deviceType = DEV_USB;
191         QueryUSBResults2 usbp;
192         err = SBIGUnivDrvCommand(CC_QUERY_USB2, 0, &usbp);
193         Debug.Write(wxString::Format("SBIG: CC_QUERY_USB2 returns %hd, camerasFound = %hu\n", err, usbp.camerasFound));
194         if (usbp.camerasFound > 1)
195         {
196             wxArrayString USBNames;
197             int i;
198             for (i = 0; i < usbp.camerasFound; i++)
199             {
200                 Debug.Write(wxString::Format("SBIG: [%d] %s\n", i, usbp.usbInfo[i].name));
201                 USBNames.Add(usbp.usbInfo[i].name);
202             }
203             i = wxGetSingleChoiceIndex(_("Select USB camera"), _("Camera name"), USBNames);
204             Debug.Write(wxString::Format("SBIG: selected index %d\n", i));
205             if (i == -1)
206                 return true;
207             odp.deviceType = DEV_USB1 + i;
208         }
209         break;
210     case 1: {
211         odp.deviceType = DEV_ETH;
212         wxString IPstr = wxGetTextFromUser(_("IP address"), _("Enter IP address"),
213             pConfig->Profile.GetString("/camera/sbig/ipaddr", _T("")));
214         Debug.Write(wxString::Format("SBIG: selected ipaddr %s\n", IPstr));
215         if (IPstr.length() == 0)
216             return true;
217         pConfig->Profile.SetString("/camera/sbig/ipaddr", IPstr);
218         wxString tmpstr = IPstr.BeforeFirst('.');
219         unsigned long tmp;
220         tmpstr.ToULong(&tmp);
221         unsigned long ip = tmp << 24;
222         IPstr = IPstr.AfterFirst('.');
223         tmpstr = IPstr.BeforeFirst('.');
224         tmpstr.ToULong(&tmp);
225         ip = ip | (tmp << 16);
226         IPstr = IPstr.AfterFirst('.');
227         tmpstr = IPstr.BeforeFirst('.');
228         tmpstr.ToULong(&tmp);
229         ip = ip | (tmp << 8);
230         IPstr = IPstr.AfterFirst('.');
231         tmpstr = IPstr.BeforeFirst('.');
232         tmpstr.ToULong(&tmp);
233         ip = ip | tmp;
234         odp.ipAddress = ip;
235         break;
236     }
237 #ifdef __WINDOWS__
238     case 2:
239         Debug.Write("SBIG: selected LPT1\n");
240         odp.deviceType = DEV_LPT1;
241         odp.lptBaseAddress = 0x378;
242         break;
243     case 3:
244         Debug.Write("SBIG: selected LPT2\n");
245         odp.deviceType = DEV_LPT2;
246         odp.lptBaseAddress = 0x278;
247         break;
248     case 4:
249         Debug.Write("SBIG: selected LPT3\n");
250         odp.deviceType = DEV_LPT3;
251         odp.lptBaseAddress = 0x3BC;
252         break;
253 #else
254     case 2:
255         Debug.Write("SBIG: selected USB1\n");
256         odp.deviceType = DEV_USB1;
257         break;
258     case 3:
259         Debug.Write("SBIG: selected USB2\n");
260         odp.deviceType = DEV_USB2;
261         break;
262     case 4:
263         Debug.Write("SBIG: selected USB3\n");
264         odp.deviceType = DEV_USB3;
265         break;
266 #endif
267     }
268 
269     pConfig->Profile.SetInt("/camera/sbig/deviceType", odp.deviceType);
270     pConfig->Profile.SetInt("/camera/sbig/ipAddress", odp.ipAddress);
271     pConfig->Profile.SetInt("/camera/sbig/lptBaseAddress", odp.lptBaseAddress);
272     pConfig->Profile.SetInt("/camera/sbig/useTrackingCCD", -1); // force prompt for tracking CCD
273 
274     return false;
275 }
276 
LoadOpenDeviceParams(OpenDeviceParams * odp)277 static bool LoadOpenDeviceParams(OpenDeviceParams *odp)
278 {
279     int deviceType = pConfig->Profile.GetInt("/camera/sbig/deviceType", -1);
280     if (deviceType == -1)
281         return false;
282 
283     odp->deviceType = deviceType;
284     odp->ipAddress = pConfig->Profile.GetInt("/camera/sbig/ipAddress", 0);
285     odp->lptBaseAddress = pConfig->Profile.GetInt("/camera/sbig/lptBaseAddress", 0);
286 
287     return true;
288 }
289 
HandleSelectCameraButtonClick(wxCommandEvent & evt)290 bool CameraSBIG::HandleSelectCameraButtonClick(wxCommandEvent& evt)
291 {
292     if (LoadDriver())
293         SelectInterfaceAndDevice();
294     return true; // handled
295 }
296 
Connect(const wxString & camId)297 bool CameraSBIG::Connect(const wxString& camId)
298 {
299     // DEAL WITH PIXEL ASPECT RATIO
300 
301     if (!LoadDriver())
302         return true;
303 
304     OpenDeviceParams odp;
305 
306     if (!LoadOpenDeviceParams(&odp))
307     {
308         bool err = SelectInterfaceAndDevice();
309         if (err)
310         {
311             Disconnect();
312             return true;
313         }
314         LoadOpenDeviceParams(&odp);
315     }
316 
317     short err;
318 
319     // Attempt connection
320     err = SBIGUnivDrvCommand(CC_OPEN_DEVICE, &odp, NULL);
321     if (err != CE_NO_ERROR)
322     {
323         Debug.Write(wxString::Format("SBIG: CC_OPEN_DEVICE err %d\n", err));
324         wxMessageBox(wxString::Format(_("Cannot open SBIG camera: Code %d"), err), _("Error"));
325         Disconnect();
326         return true;
327     }
328 
329     // Establish link
330     EstablishLinkResults elr;
331     err = SBIGUnivDrvCommand(CC_ESTABLISH_LINK, NULL, &elr);
332     if (err != CE_NO_ERROR)
333     {
334         Debug.Write(wxString::Format("SBIG: CC_ESTABLISH_LINK err %d\n", err));
335         wxMessageBox(wxString::Format(_("Link to SBIG camera failed: Code %d"), err), _("Error"));
336         Disconnect();
337         return true;
338     }
339 
340     // Determine if there is a tracking CCD
341     m_useTrackingCCD = false;
342     GetCCDInfoParams gcip;
343     GetCCDInfoResults0 gcir0;
344     gcip.request = CCD_INFO_TRACKING;
345     err = SBIGUnivDrvCommand(CC_GET_CCD_INFO, &gcip, &gcir0);
346     if (err == CE_NO_ERROR)
347     {
348         int val = pConfig->Profile.GetInt("/camera/sbig/useTrackingCCD", -1);
349         if (val == -1)
350         {
351             int resp = wxMessageBox(wxString::Format(_("Tracking CCD found, use it?\n\nNo = use main image CCD")),
352                 _("CCD Choice"), wxYES_NO | wxICON_QUESTION);
353             if (resp == wxYES)
354                 m_useTrackingCCD = true;
355             pConfig->Profile.SetInt("/camera/sbig/useTrackingCCD", m_useTrackingCCD ? 1 : 0);
356         }
357         else
358         {
359             m_useTrackingCCD = !!val;
360             Debug.Write(wxString::Format("SBIG: using saved val m_useTrackingCCD = %d\n", m_useTrackingCCD));
361         }
362     }
363     if (!m_useTrackingCCD)
364     {
365         gcip.request = CCD_INFO_IMAGING;
366         err = SBIGUnivDrvCommand(CC_GET_CCD_INFO, &gcip, &gcir0);
367         if (err != CE_NO_ERROR)
368         {
369             Debug.Write(wxString::Format("SBIG: CC_GET_CCD_INFO err %d\n", err));
370             wxMessageBox(_("Error getting info on main CCD"), _("Error"));
371             Disconnect();
372             return true;
373         }
374     }
375 
376     MaxBinning = 1;
377     m_devicePixelSize = 0.0;
378     for (int i = 0; i < gcir0.readoutModes; i++)
379     {
380         int mode = gcir0.readoutInfo[i].mode;
381         if (mode == RM_1X1 || mode == RM_2X2)
382         {
383             int idx = mode == RM_1X1 ? 0 : 1;
384             m_imageSize[idx] = wxSize(gcir0.readoutInfo[i].width, gcir0.readoutInfo[i].height);
385             if (mode == RM_1X1)
386             {
387                 unsigned long bcd = wxMax(gcir0.readoutInfo[i].pixelWidth, gcir0.readoutInfo[i].pixelHeight);
388                 m_devicePixelSize = (double) bcd2long(bcd) / 100.0;
389             }
390             else // RM_2x2
391                 MaxBinning = 2;
392         }
393     }
394 
395     if (Binning > MaxBinning)
396         Binning = MaxBinning;
397 
398     FullSize = m_imageSize[Binning - 1];
399 
400     IsColor = false;
401 
402     if (!m_useTrackingCCD)
403     {
404         GetCCDInfoResults6 gcir6;
405         gcip.request = CCD_INFO_EXTENDED3;
406         err = SBIGUnivDrvCommand(CC_GET_CCD_INFO, &gcip, &gcir6);
407         if (err == CE_NO_ERROR)
408         {
409             IsColor = gcir6.ccdBits & 1;  // b0 set indicates color CCD
410         }
411     }
412 
413     Name = gcir0.name;
414     if (Name.Find("Color") != wxNOT_FOUND)
415     {
416         IsColor = true;
417     }
418 
419     Debug.Write(wxString::Format("SBIG: %s type=%u, UseTrackingCCD=%d, MaxBin = %hu, 1x1 size %d x %d, 2x2 size %d x %d IsColor %d\n",
420         gcir0.name, gcir0.cameraType, m_useTrackingCCD, MaxBinning, m_imageSize[0].x, m_imageSize[0].y, m_imageSize[1].x, m_imageSize[1].y,
421         IsColor));
422 
423     Connected = true;
424     return false;
425 }
426 
Disconnect()427 bool CameraSBIG::Disconnect()
428 {
429     SBIGUnivDrvCommand(CC_CLOSE_DEVICE, NULL, NULL);
430     SBIGUnivDrvCommand(CC_CLOSE_DRIVER, NULL, NULL);
431     m_driverLoaded = false;
432     Connected = false;
433     return false;
434 }
435 
GetDevicePixelSize(double * devPixelSize)436 bool CameraSBIG::GetDevicePixelSize(double *devPixelSize)
437 {
438     if (!Connected)
439         return true;
440 
441     *devPixelSize = m_devicePixelSize;
442     return false;
443 }
444 
InitCapture()445 void CameraSBIG::InitCapture()
446 {
447     // Set gain
448 }
449 
StopExposure(EndExposureParams * eep)450 static bool StopExposure(EndExposureParams *eep)
451 {
452     short err = SBIGUnivDrvCommand(CC_END_EXPOSURE, eep, NULL);
453     return err == CE_NO_ERROR;
454 }
455 
Capture(int duration,usImage & img,int options,const wxRect & subframe)456 bool CameraSBIG::Capture(int duration, usImage& img, int options, const wxRect& subframe)
457 {
458     bool TakeSubframe = UseSubframes;
459 
460     FullSize = m_imageSize[Binning - 1];
461 
462     if (subframe.width <= 0 || subframe.height <= 0 || subframe.GetRight() >= FullSize.GetWidth() || subframe.GetBottom() >= FullSize.GetHeight())
463     {
464         TakeSubframe = false;
465     }
466 
467     StartExposureParams2 sep;
468     EndExposureParams eep;
469     QueryCommandStatusParams qcsp;
470     QueryCommandStatusResults qcsr;
471     ReadoutLineParams rlp;
472     DumpLinesParams dlp;
473 
474     if (m_useTrackingCCD)
475     {
476         sep.ccd      = CCD_TRACKING;
477         sep.abgState = ABG_CLK_LOW7;
478         eep.ccd = CCD_TRACKING;
479         rlp.ccd = CCD_TRACKING;
480         dlp.ccd = CCD_TRACKING;
481     }
482     else
483     {
484         sep.ccd      = CCD_IMAGING;
485         sep.abgState = ABG_LOW7;
486         eep.ccd = CCD_IMAGING;
487         rlp.ccd = CCD_IMAGING;
488         dlp.ccd = CCD_IMAGING;
489     }
490 
491     sep.exposureTime = (unsigned long) duration / 10;
492     sep.openShutter = ShutterClosed ? SC_CLOSE_SHUTTER : SC_OPEN_SHUTTER;
493     sep.readoutMode = rlp.readoutMode = dlp.readoutMode =
494         Binning == 1 ? RM_1X1 : RM_2X2;
495 
496     if (TakeSubframe)
497     {
498         sep.top = subframe.x;
499         sep.width = subframe.width;
500         sep.left = subframe.y;
501         sep.height = subframe.height;
502     }
503     else
504     {
505         sep.top = 0;
506         sep.left = 0;
507         sep.width = (unsigned short) FullSize.GetWidth();
508         sep.height = (unsigned short) FullSize.GetHeight();
509     }
510 
511     // init memory
512     if (img.Init(FullSize))
513     {
514         DisconnectWithAlert(CAPT_FAIL_MEMORY);
515         return true;
516     }
517 
518     // Start exposure
519 
520     short err = SBIGUnivDrvCommand(CC_START_EXPOSURE2, &sep, NULL);
521     if (err != CE_NO_ERROR)
522     {
523         DisconnectWithAlert(_("Cannot start exposure"), NO_RECONNECT);
524         return true;
525     }
526 
527     CameraWatchdog watchdog(duration, GetTimeoutMs());
528 
529     if (duration > 100)
530     {
531         // wait until near end of exposure
532         if (WorkerThread::MilliSleep(duration - 100, WorkerThread::INT_ANY) &&
533             (WorkerThread::TerminateRequested() || StopExposure(&eep)))
534         {
535             return true;
536         }
537     }
538 
539     qcsp.command = CC_START_EXPOSURE;
540     while (true)
541     {
542         // wait for image to finish and d/l
543         wxMilliSleep(20);
544         err = SBIGUnivDrvCommand(CC_QUERY_COMMAND_STATUS, &qcsp, &qcsr);
545         if (err != CE_NO_ERROR)
546         {
547             DisconnectWithAlert(_("Cannot poll exposure"), NO_RECONNECT);
548             return true;
549         }
550         if (m_useTrackingCCD)
551             qcsr.status = qcsr.status >> 2;
552         if (qcsr.status == CS_INTEGRATION_COMPLETE)
553             break;
554         if (WorkerThread::InterruptRequested())
555         {
556             StopExposure(&eep);
557             return true;
558         }
559         if (watchdog.Expired())
560         {
561             StopExposure(&eep);
562             DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
563             return true;
564         }
565     }
566 
567     // End exposure
568     if (!StopExposure(&eep))
569     {
570         DisconnectWithAlert(_("Cannot stop exposure"), NO_RECONNECT);
571         return true;
572     }
573 
574     // Get data
575 
576     if (TakeSubframe)
577     {
578         img.Subframe = subframe;
579 
580         // dump the lines above the one we want
581         dlp.lineLength = subframe.y;
582         SBIGUnivDrvCommand(CC_DUMP_LINES, &dlp, NULL);
583 
584         // set up to read the part of the lines we do want
585         rlp.pixelStart  = subframe.x;
586         rlp.pixelLength = subframe.width;
587 
588         img.Clear();
589 
590         for (int y = 0; y < subframe.height; y++)
591         {
592             unsigned short *dataptr = img.ImageData + subframe.x + (y + subframe.y) * FullSize.GetWidth();
593             err = SBIGUnivDrvCommand(CC_READOUT_LINE, &rlp, dataptr);
594             if (err != CE_NO_ERROR)
595             {
596                 DisconnectWithAlert(_("Error downloading data"), NO_RECONNECT);
597                 return true;
598             }
599         }
600     }
601     else
602     {
603         rlp.pixelStart  = 0;
604         rlp.pixelLength = (unsigned short) FullSize.GetWidth();
605         unsigned short *dataptr = img.ImageData;
606         for (int y = 0; y < FullSize.GetHeight(); y++)
607         {
608             err = SBIGUnivDrvCommand(CC_READOUT_LINE, &rlp, dataptr);
609             dataptr += FullSize.GetWidth();
610             if (err != CE_NO_ERROR)
611             {
612                 DisconnectWithAlert(_("Error downloading data"), NO_RECONNECT);
613                 return true;
614             }
615         }
616     }
617 
618     if (options & CAPTURE_SUBTRACT_DARK)
619         SubtractDark(img);
620     if (IsColor && Binning == 1 && (options & CAPTURE_RECON))
621         QuickLRecon(img);
622 
623     return false;
624 }
625 
ST4PulseGuideScope(int direction,int duration)626 bool CameraSBIG::ST4PulseGuideScope(int direction, int duration)
627 {
628     ActivateRelayParams rp;
629     rp.tXMinus = rp.tXPlus = rp.tYMinus = rp.tYPlus = 0;
630     unsigned short dur = duration / 10;
631     switch (direction) {
632         case WEST: rp.tXMinus = dur; break;
633         case EAST: rp.tXPlus = dur; break;
634         case NORTH: rp.tYMinus = dur; break;
635         case SOUTH: rp.tYPlus = dur; break;
636     }
637 
638     short err = SBIGUnivDrvCommand(CC_ACTIVATE_RELAY, &rp, NULL);
639     if (err != CE_NO_ERROR) return true;
640 
641     if (duration > 60) wxMilliSleep(duration - 50);
642 
643     QueryCommandStatusParams qcsp;
644     qcsp.command = CC_ACTIVATE_RELAY;
645 
646     MountWatchdog watchdog(duration, 5000);
647 
648     while (true) {  // wait for pulse to finish
649         wxMilliSleep(10);
650         QueryCommandStatusResults qcsr;
651         err = SBIGUnivDrvCommand(CC_QUERY_COMMAND_STATUS, &qcsp, &qcsr);
652         if (err != CE_NO_ERROR) {
653             pFrame->Alert(_("Cannot check SBIG relay status"));
654             return true;
655         }
656         if (!qcsr.status)
657             break;
658         if (WorkerThread::TerminateRequested())
659             return true;
660         if (watchdog.Expired())
661         {
662             pFrame->Alert(_("Timeout expired waiting for guide pulse to complete."));
663             return true;
664         }
665     }
666 
667     return false;
668 }
669 
MakeSBIGCamera()670 GuideCamera *SBIGCameraFactory::MakeSBIGCamera()
671 {
672     return new CameraSBIG();
673 }
674 
675 #endif
676