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