1 /*
2  *  cam_altair.cpp
3  *  PHD Guiding
4  *
5  *  Created by Robin Glover.
6  *  Copyright (c) 2014 Robin Glover
7  *  Copyright (c) 2018 Andy Galasso
8  *  All rights reserved.
9  *
10  *  This source code is distributed under the following "BSD" license
11  *  Redistribution and use in source and binary forms, with or without
12  *  modification, are permitted provided that the following conditions are met:
13  *    Redistributions of source code must retain the above copyright notice,
14  *     this list of conditions and the following disclaimer.
15  *    Redistributions in binary form must reproduce the above copyright notice,
16  *     this list of conditions and the following disclaimer in the
17  *     documentation and/or other materials provided with the distribution.
18  *    Neither the name of openphdguiding.org nor the names of its
19  *     contributors may be used to endorse or promote products derived from
20  *     this software without specific prior written permission.
21  *
22  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  *  POSSIBILITY OF SUCH DAMAGE.
33  *
34  */
35 
36 #include "phd.h"
37 
38 #ifdef ALTAIR
39 
40 #include "cam_altair.h"
41 #include "altaircam.h"
42 
43 #ifdef __WINDOWS__
44 
45 struct SDKLib
46 {
47     HMODULE m_module;
48 
49 #define SDK(f) \
50     decltype(Altaircam_ ## f) *f;
51 #define SDK_OPT(f) SDK(f)
52 # include "cameras/altaircam_sdk.h"
53 #undef SDK
54 #undef SDK_OPT
55 
SDKLibSDKLib56     SDKLib() : m_module(nullptr) { }
~SDKLibSDKLib57     ~SDKLib() { Unload(); }
58 
_LoadSDKLib59     bool _Load(LPCTSTR filename, const char *prefix)
60     {
61         if (m_module)
62             return true;
63 
64         Debug.Write(wxString::Format("Altair: loading %s\n", filename));
65 
66         m_module = LoadLibrary(filename);
67         if (!m_module)
68         {
69             Debug.Write(wxString::Format("Altair: could not load library %s\n", filename));
70             return false;
71         }
72 
73         try
74         {
75 #define _GPA(f) \
76             std::ostringstream os; \
77             os << prefix << #f; \
78             std::string name = os.str(); \
79             f = reinterpret_cast<decltype(Altaircam_ ## f) *>(GetProcAddress(m_module, name.c_str()))
80 #define SDK(f) do { \
81             _GPA(f); \
82             if (!f) \
83                 throw name; \
84         } while (false);
85 #define SDK_OPT(f) do { \
86             _GPA(f); \
87         } while (false);
88 # include "cameras/altaircam_sdk.h"
89 #undef SDK
90 #undef SDK_OPT
91 #undef _GPA
92         }
93         catch (const std::string& name)
94         {
95             Debug.Write(wxString::Format("Altair: %s missing symbol %s\n", filename, name.c_str()));
96             Unload();
97             return false;
98         }
99 
100         Debug.Write(wxString::Format("Altair: SDK version %s\n", Version()));
101 
102         return true;
103     }
104 
LoadSDKLib105     bool Load()
106     {
107         return _Load(_T("AltairCam.dll"), "Altaircam_");
108     }
109 
LoadLegacySDKLib110     bool LoadLegacy()
111     {
112         return _Load(_T("AltairCam_legacy.dll"), "Toupcam_");
113     }
114 
UnloadSDKLib115     void Unload()
116     {
117         if (m_module)
118         {
119             FreeLibrary(m_module);
120             m_module = nullptr;
121         }
122     }
123 };
124 
125 #endif // __WINDOWS__
126 
127 struct AltairCamera : public GuideCamera
128 {
129     enum { MAX_DISCARD_FRAMES = 5 };
130 
131     AltairCamType m_type;
132     SDKLib m_sdk;
133     wxRect m_frame;
134     unsigned char *m_buffer;
135     bool m_isColor;
136     bool m_capturing;
137     unsigned int m_discardCnt;
138     int m_minGain;
139     int m_maxGain;
140     double m_devicePixelSize;
141     HAltaircam m_handle;
142     volatile bool m_frameReady;
143     bool m_reduceResolution;
144     unsigned int m_framesToDiscard;
145 
146     AltairCamera(AltairCamType type);
147     ~AltairCamera();
148 
149     bool LoadSDK();
150 
CanSelectCameraAltairCamera151     bool CanSelectCamera() const override { return true; }
152     bool EnumCameras(wxArrayString& names, wxArrayString& ids) override;
153     bool Capture(int duration, usImage& img, int options, const wxRect& subframe) override;
154     bool Connect(const wxString& camId) override;
155     bool Disconnect() override;
156 
157     bool ST4PulseGuideScope(int direction, int duration) override;
158     void ClearGuidePort();
159 
160     void ShowPropertyDialog() override;
161 
HasNonGuiCaptureAltairCamera162     bool HasNonGuiCapture() override { return true; }
ST4HasNonGuiMoveAltairCamera163     bool ST4HasNonGuiMove() override { return true; }
164     wxByte BitsPerPixel() override;
165     bool GetDevicePixelSize(double *devPixelSize) override;
166 
167     void StopCapture();
168 };
169 
170 class AltairCameraDlg : public wxDialog
171 {
172 public:
173     wxCheckBox *m_reduceRes;
174     wxSpinCtrl *m_framesToDiscard;
175 
176     AltairCameraDlg(wxWindow *parent);
~AltairCameraDlg()177     ~AltairCameraDlg() { }
178 };
179 
AltairCameraDlg(wxWindow * parent)180 AltairCameraDlg::AltairCameraDlg(wxWindow *parent)
181     : wxDialog(parent, wxID_ANY, _("Altair Camera Settings"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
182 {
183     SetSizeHints(wxDefaultSize, wxDefaultSize);
184 
185     wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);
186 
187     wxStaticBoxSizer *sbSizer3 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Settings")), wxVERTICAL);
188 
189     wxBoxSizer *sizer1 = new wxBoxSizer(wxHORIZONTAL);
190     m_reduceRes = new wxCheckBox(this, wxID_ANY,
191             wxString::Format(_("Reduced Resolution (by ~%d%%)"), 20),
192             wxDefaultPosition, wxDefaultSize, 0);
193     sizer1->Add(m_reduceRes, 0, wxALL, 5);
194     sbSizer3->Add(sizer1);
195 
196     wxBoxSizer *sizer2 = new wxBoxSizer(wxHORIZONTAL);
197     wxStaticText *txt1 = new wxStaticText(this, wxID_ANY, _("Discard Frames"));
198     sizer2->Add(txt1, 0, wxALL, 5);
199     int width = StringWidth(this, _T("00"));
200     m_framesToDiscard = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(width, -1),
201         wxSP_ARROW_KEYS, 0, AltairCamera::MAX_DISCARD_FRAMES, 0);
202     m_framesToDiscard->SetToolTip(_("Discard this many frames whan capturing starts. "
203         "Useful for preventing initial under-exposed frames interfering with automatic star selection."));
204     sizer2->Add(m_framesToDiscard, 0, wxALL, 5);
205     sbSizer3->Add(sizer2);
206 
207     top_sizer->Add(sbSizer3, 1, wxEXPAND, 5);
208 
209     wxStdDialogButtonSizer *sdbSizer2 = new wxStdDialogButtonSizer();
210     wxButton *sdbSizer2OK = new wxButton(this, wxID_OK);
211     wxButton *sdbSizer2Cancel = new wxButton(this, wxID_CANCEL);
212     sdbSizer2->AddButton(sdbSizer2OK);
213     sdbSizer2->AddButton(sdbSizer2Cancel);
214     sdbSizer2->Realize();
215 
216     top_sizer->Add(sdbSizer2, 0, wxALL | wxEXPAND, 5);
217 
218     SetSizer(top_sizer);
219     Layout();
220     Fit();
221 
222     Centre(wxBOTH);
223 }
224 
GetConfigDiscardFrames()225 static int GetConfigDiscardFrames()
226 {
227     int n = pConfig->Profile.GetInt("/camera/Altair/DiscardFrames", 0);
228     return wxMax(0, wxMin((int) AltairCamera::MAX_DISCARD_FRAMES, n));
229 }
230 
AltairCamera(AltairCamType type)231 AltairCamera::AltairCamera(AltairCamType type)
232     :
233     m_type(type),
234     m_buffer(nullptr),
235     m_capturing(false)
236 {
237     Name = _T("Altair Camera");
238     Connected = false;
239     m_hasGuideOutput = true;
240     HasSubframes = false;
241     HasGainControl = true; // workaround: ok to set to false later, but brain dialog will crash if we start false then change to true later when the camera is connected
242     PropertyDialogType = PROPDLG_WHEN_DISCONNECTED;
243 
244     this->m_framesToDiscard = GetConfigDiscardFrames();
245 }
246 
~AltairCamera()247 AltairCamera::~AltairCamera()
248 {
249     delete[] m_buffer;
250 }
251 
BitsPerPixel()252 wxByte AltairCamera::BitsPerPixel()
253 {
254     return 8;
255 }
256 
cam_gain(int minval,int maxval,int pct)257 inline static int cam_gain(int minval, int maxval, int pct)
258 {
259     return minval + pct * (maxval - minval) / 100;
260 }
261 
gain_pct(int minval,int maxval,int val)262 inline static int gain_pct(int minval, int maxval, int val)
263 {
264     return (val - minval) * 100 / (maxval - minval);
265 }
266 
Enum(const SDKLib & sdk,AltaircamDeviceV2 inst[ALTAIRCAM_MAX])267 static unsigned int Enum(const SDKLib& sdk, AltaircamDeviceV2 inst[ALTAIRCAM_MAX])
268 {
269     if (sdk.EnumV2)
270         return sdk.EnumV2(inst);
271 
272     static AltaircamModelV2 s_model[ALTAIRCAM_MAX];
273     static AltaircamDevice s_inst1[ALTAIRCAM_MAX];
274 
275     unsigned int count = sdk.Enum(s_inst1);
276     for (unsigned int i = 0; i < count; i++)
277     {
278         memcpy(&inst[i].displayname[0], &s_inst1[i].displayname[0], sizeof(inst[i].displayname));
279         memcpy(&inst[i].id[0], &s_inst1[i].id[0], sizeof(inst[i].id));
280         s_model[i].name = s_inst1[i].model->name;
281         s_model[i].flag = s_inst1[i].model->flag;
282         s_model[i].maxspeed = s_inst1[i].model->maxspeed;
283         s_model[i].preview = s_inst1[i].model->preview;
284         s_model[i].still = 0; // unknwown
285         s_model[i].maxfanspeed = 0; // unknown
286         s_model[i].ioctrol = 0;
287         s_model[i].xpixsz = 0.f;
288         s_model[i].ypixsz = 0.f;
289         memcpy(&s_model[i].res[0], &s_inst1[i].model->res[0], sizeof(s_model[i].res));
290         inst[i].model = &s_model[i];
291     }
292 
293     return count;
294 }
295 
LoadSDK()296 bool AltairCamera::LoadSDK()
297 {
298     switch (m_type)
299     {
300     default:
301     case ALTAIR_CAM_CURRENT:
302         return m_sdk.Load();
303 
304     case ALTAIR_CAM_LEGACY:
305         return m_sdk.LoadLegacy();
306     }
307 }
308 
EnumCameras(wxArrayString & names,wxArrayString & ids)309 bool AltairCamera::EnumCameras(wxArrayString& names, wxArrayString& ids)
310 {
311     if (!LoadSDK())
312         return true;
313 
314     AltaircamDeviceV2 ai[ALTAIRCAM_MAX];
315     unsigned numCameras = Enum(m_sdk, ai);
316 
317     for (int i = 0; i < numCameras; i++)
318     {
319         names.Add(ai[i].displayname);
320         ids.Add(ai[i].id);
321     }
322 
323     return false;
324 }
325 
Connect(const wxString & camIdArg)326 bool AltairCamera::Connect(const wxString& camIdArg)
327 {
328     if (!LoadSDK())
329         return true;
330 
331     AltaircamDeviceV2 ainst[ALTAIRCAM_MAX];
332     unsigned int numCameras = Enum(m_sdk, ainst);
333 
334     if (numCameras == 0)
335     {
336         return CamConnectFailed(_("No Altair cameras detected"));
337     }
338 
339     wxString camId(camIdArg);
340     if (camId == DEFAULT_CAMERA_ID)
341         camId = ainst[0].id;
342 
343     const AltaircamDeviceV2 *pai = nullptr;
344     for (unsigned int i = 0; i < numCameras; i++)
345     {
346         if (camId == ainst[i].id)
347         {
348             pai = &ainst[i];
349             break;
350         }
351     }
352     if (!pai)
353     {
354         return CamConnectFailed(_("Specified Altair Camera not found."));
355     }
356 
357     m_handle = m_sdk.Open(camId);
358     if (!m_handle)
359     {
360         return CamConnectFailed(_("Failed to open Altair Camera."));
361     }
362 
363     Connected = true;
364 
365     Name = pai->displayname;
366     bool hasROI = (pai->model->flag & ALTAIRCAM_FLAG_ROI_HARDWARE) != 0;
367     bool hasSkip = (pai->model->flag & ALTAIRCAM_FLAG_BINSKIP_SUPPORTED) != 0;
368     m_isColor = (pai->model->flag & ALTAIRCAM_FLAG_MONO) == 0;
369 
370     Debug.Write(wxString::Format("ALTAIR: isColor = %d, hasROI = %d, hasSkip = %d\n",
371                                  m_isColor, hasROI, hasSkip));
372 
373     int width, height;
374     if (FAILED(m_sdk.get_Resolution(m_handle, 0, &width, &height)))
375     {
376         Disconnect();
377         return CamConnectFailed(_("Failed to get camera resolution for Altair Camera."));
378     }
379 
380     delete[] m_buffer;
381     m_buffer = new unsigned char[width * height]; // new SDK has issues with some ROI functions needing full resolution buffer size
382 
383     m_reduceResolution = pConfig->Profile.GetBoolean("/camera/Altair/ReduceResolution", false);
384     if (hasROI && m_reduceResolution)
385     {
386         width *= 0.8;
387         height *= 0.8;
388     }
389 
390     FullSize.x = width;
391     FullSize.y = height;
392 
393     float xSize, ySize;
394     m_devicePixelSize = 3.75; // for all cameras so far....
395     if (m_sdk.get_PixelSize(m_handle, 0, &xSize, &ySize) == 0)
396     {
397         m_devicePixelSize = xSize;
398     }
399 
400     HasGainControl = false;
401 
402     unsigned short min, max, def;
403     if (SUCCEEDED(m_sdk.get_ExpoAGainRange(m_handle, &min, &max, &def)))
404     {
405         m_minGain = min;
406         m_maxGain = max;
407         HasGainControl = max > min;
408     }
409 
410     m_sdk.put_Speed(m_handle, 0);
411     m_sdk.put_RealTime(m_handle, TRUE);
412 
413     if (hasSkip)
414         m_sdk.put_Mode(m_handle, 0);
415 
416     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_RAW, 1);
417 
418 #if 0
419     // TODO: this is the initiailization code copied from cam_touptek.cpp
420     // I was hoping this one of these might help with the problem of the first
421     // frame exposure being very low, but it had no effect. Leaving these
422     // disabled for now rather than risk introducing a change that is
423     // incompatible with one of the camera models I am unable to test with.
424     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_PROCESSMODE, 0);
425     //m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_BITDEPTH, m_cam.m_bpp == 8 ? 0 : 1);
426     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_LINEAR, 0);
427     //m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_CURVE, 0); // resetting this one fails on all the cameras I have
428     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_COLORMATIX, 0);
429     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_WBGAIN, 0);
430     //m_sdk.put_Option(ALTAIRCAM_OPTION_TRIGGER, 1);  // software trigger
431     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_AUTOEXP_POLICY, 0); // 0="Exposure Only" 1="Exposure Preferred"
432     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_ROTATE, 0);
433     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_UPSIDE_DOWN, 0);
434     //m_cam.SetOption(m_handle, ALTAIRCAM_OPTION_CG, 0); // "Conversion Gain" 0=LCG 1=HCG 2=HDR // setting this fails
435     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_FFC, 0);
436     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_DFC, 0);
437     m_sdk.put_Option(m_handle, ALTAIRCAM_OPTION_SHARPENING, 0);
438 #endif
439 
440     m_sdk.put_AutoExpoEnable(m_handle, 0);
441 
442     m_frame = wxRect(FullSize);
443 
444     Debug.Write(wxString::Format("Altair: frame (%d,%d)+(%d,%d)\n",
445         m_frame.x, m_frame.y, m_frame.width, m_frame.height));
446 
447     if (hasROI && m_reduceResolution)
448     {
449         m_sdk.put_Roi(m_handle, 0, 0, width, height);
450     }
451 
452     return false;
453 }
454 
StopCapture()455 void AltairCamera::StopCapture()
456 {
457     if (m_capturing)
458     {
459         Debug.AddLine("Altair: stopcapture");
460         m_sdk.Stop(m_handle);
461         m_capturing = false;
462     }
463 }
464 
GetDevicePixelSize(double * devPixelSize)465 bool AltairCamera::GetDevicePixelSize(double *devPixelSize)
466 {
467     if (!Connected)
468         return true;
469 
470     *devPixelSize = m_devicePixelSize;
471     return false;                               // Pixel size is known in any case
472 }
473 
ShowPropertyDialog()474 void AltairCamera::ShowPropertyDialog()
475 {
476     AltairCameraDlg dlg(wxGetApp().GetTopWindow());
477 
478     bool value = pConfig->Profile.GetBoolean("/camera/Altair/ReduceResolution", false);
479     dlg.m_reduceRes->SetValue(value);
480 
481     int n = GetConfigDiscardFrames();
482     dlg.m_framesToDiscard->SetValue(n);
483 
484     if (dlg.ShowModal() == wxID_OK)
485     {
486         m_reduceResolution = dlg.m_reduceRes->GetValue();
487         pConfig->Profile.SetBoolean("/camera/Altair/ReduceResolution", m_reduceResolution);
488 
489         m_framesToDiscard = dlg.m_framesToDiscard->GetValue();
490         pConfig->Profile.SetInt("/camera/Altair/DiscardFrames", m_framesToDiscard);
491     }
492 }
493 
Disconnect()494 bool AltairCamera::Disconnect()
495 {
496     StopCapture();
497     m_sdk.Close(m_handle);
498 
499     Connected = false;
500 
501     delete[] m_buffer;
502     m_buffer = nullptr;
503 
504     return false;
505 }
506 
round_down(int v,int m)507 inline static int round_down(int v, int m)
508 {
509     return v & ~(m - 1);
510 }
511 
round_up(int v,int m)512 inline static int round_up(int v, int m)
513 {
514     return round_down(v + m - 1, m);
515 }
516 
517 
CameraCallback(unsigned int event,void * pCallbackCtx)518 void __stdcall CameraCallback(unsigned int event, void *pCallbackCtx)
519 {
520     if (event == ALTAIRCAM_EVENT_IMAGE)
521     {
522         AltairCamera *cam = (AltairCamera *) pCallbackCtx;
523         cam->m_frameReady = true;
524     }
525 }
526 
527 //static void flush_buffered_image(int cameraId, usImage& img)
528 //{
529 //    enum { NUM_IMAGE_BUFFERS = 2 }; // camera has 2 internal frame buffers
530 //
531 //    // clear buffered frames if any
532 //
533 //    for (unsigned int num_cleared = 0; num_cleared < NUM_IMAGE_BUFFERS; num_cleared++)
534 //    {
535 //        ASI_ERROR_CODE status = ASIGetVideoData(cameraId, (unsigned char *) img.ImageData, img.NPixels * sizeof(unsigned short), 0);
536 //        if (status != ASI_SUCCESS)
537 //            break; // no more buffered frames
538 //
539 //        Debug.Write(wxString::Format("Altair: getimagedata clearbuf %u ret %d\n", num_cleared + 1, status));
540 //    }
541 //}
542 
Capture(int duration,usImage & img,int options,const wxRect & subframe)543 bool AltairCamera::Capture(int duration, usImage& img, int options, const wxRect& subframe)
544 {
545     if (img.Init(FullSize))
546     {
547         DisconnectWithAlert(CAPT_FAIL_MEMORY);
548         return true;
549     }
550 
551     wxRect frame;
552     frame = wxRect(FullSize);
553 
554     long exposureUS = duration * 1000;
555     unsigned int cur_exp;
556     if (m_sdk.get_ExpoTime(m_handle, &cur_exp) == 0 &&
557         cur_exp != exposureUS)
558     {
559         Debug.Write(wxString::Format("Altair: set CONTROL_EXPOSURE %d\n", exposureUS));
560         m_sdk.put_ExpoTime(m_handle, exposureUS);
561     }
562 
563     long new_gain = cam_gain(m_minGain, m_maxGain, GuideCameraGain);
564     unsigned short cur_gain;
565     if (m_sdk.get_ExpoAGain(m_handle, &cur_gain) == 0 &&
566         new_gain != cur_gain)
567     {
568         Debug.Write(wxString::Format("Altair: set CONTROL_GAIN %d%% %d\n", GuideCameraGain, new_gain));
569         m_sdk.put_ExpoAGain(m_handle, new_gain);
570     }
571 
572     // the camera and/or driver will buffer frames and return the oldest frame,
573     // which could be quite stale. read out all buffered frames so the frame we
574     // get is current
575 
576     //flush_buffered_image(m_handle, img);
577     unsigned int width, height;
578     while (SUCCEEDED(m_sdk.PullImage(m_handle, m_buffer, 8, &width, &height)))
579     {
580     }
581 
582     if (!m_capturing)
583     {
584         Debug.AddLine("Altair: startcapture");
585         m_frameReady = false;
586         HRESULT result = m_sdk.StartPullModeWithCallback(m_handle, CameraCallback, this);
587         if (result != 0)
588         {
589             Debug.Write(wxString::Format("Altaircam_StartPullModeWithCallback failed with code %d\n", result));
590             return true;
591         }
592         m_capturing = true;
593         m_discardCnt = m_framesToDiscard;
594     }
595 
596     int frameSize = frame.GetWidth() * frame.GetHeight();
597 
598     int poll = wxMin(duration, 100);
599 
600     while (true) // frame discard loop
601     {
602         CameraWatchdog watchdog(duration, duration + GetTimeoutMs() + 10000); // total timeout is 2 * duration + 15s (typically)
603 
604         // do not wait here, as we will miss a frame most likely, leading to poor flow of frames.
605 //        if (WorkerThread::MilliSleep(duration, WorkerThread::INT_ANY) &&
606 //            (WorkerThread::TerminateRequested() || StopCapture()))
607 //        {
608 //            return true;
609 //        }
610 
611         while (true) // PullImage retry loop
612         {
613             if (m_frameReady)
614             {
615                 m_frameReady = false;
616 
617                 if (SUCCEEDED(m_sdk.PullImage(m_handle, m_buffer, 8, &width, &height)))
618                     break;
619             }
620             WorkerThread::MilliSleep(poll, WorkerThread::INT_ANY);
621             if (WorkerThread::InterruptRequested())
622             {
623                 StopCapture();
624                 return true;
625             }
626             if (watchdog.Expired())
627             {
628                 Debug.AddLine("Altair: getimagedata failed");
629                 StopCapture();
630                 DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
631                 return true;
632             }
633         }
634 
635         if (!m_discardCnt)
636             break;
637 
638         Debug.Write(wxString::Format("Altair: discard frame %u\n", m_discardCnt));
639 
640         --m_discardCnt;
641 
642     } // discard loop
643 
644     for (unsigned int i = 0; i < img.NPixels; i++)
645         img.ImageData[i] = m_buffer[i];
646 
647     if (options & CAPTURE_SUBTRACT_DARK)
648         SubtractDark(img);
649     if (m_isColor && Binning == 1 && (options & CAPTURE_RECON))
650         QuickLRecon(img);
651 
652     return false;
653 }
654 
GetAltairDirection(int direction)655 inline static int GetAltairDirection(int direction)
656 {
657     switch (direction)
658     {
659     default:
660     case NORTH:
661         return 0;
662     case EAST:
663         return 2;
664     case WEST:
665         return 3;
666     case SOUTH:
667         return 1;
668     }
669 }
670 
ST4PulseGuideScope(int direction,int duration)671 bool AltairCamera::ST4PulseGuideScope(int direction, int duration)
672 {
673     int d = GetAltairDirection(direction);
674     m_sdk.ST4PlusGuide(m_handle, d, duration);
675 
676     return false;
677 }
678 
ClearGuidePort()679 void AltairCamera::ClearGuidePort()
680 {
681     m_sdk.ST4PlusGuide(m_handle, 0, 0);
682 }
683 
MakeAltairCamera(AltairCamType type)684 GuideCamera *AltairCameraFactory::MakeAltairCamera(AltairCamType type)
685 {
686     return new AltairCamera(type);
687 }
688 
689 #endif // Altaircam_ASI
690