1 /*
2 *  cam_svb.cpp
3 *  PHD2 Guiding
4 *
5 *  Copyright (c) 2020 Andy Galasso
6 *  All rights reserved.
7 *
8 *  This source code is distributed under the following "BSD" license
9 *  Redistribution and use in source and binary forms, with or without
10 *  modification, are permitted provided that the following conditions are met:
11 *    Redistributions of source code must retain the above copyright notice,
12 *     this list of conditions and the following disclaimer.
13 *    Redistributions in binary form must reproduce the above copyright notice,
14 *     this list of conditions and the following disclaimer in the
15 *     documentation and/or other materials provided with the distribution.
16 *    Neither the name of openphdguiding.org nor the names of its
17 *     contributors may be used to endorse or promote products derived from
18 *     this software without specific prior written permission.
19 *
20 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 *  POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33 #include "phd.h"
34 
35 #ifdef SVB_CAMERA
36 
37 #include "cam_svb.h"
38 #include "cameras/SVBCameraSDK.h"
39 
40 #ifdef __WINDOWS__
41 # include <Shlwapi.h>
42 # include <DelayImp.h>
43 #endif
44 
45 enum CaptureMode
46 {
47     CM_SNAP,
48     CM_VIDEO,
49 };
50 
51 class SVBCamera : public GuideCamera
52 {
53     wxRect m_maxSize;
54     wxRect m_frame;
55     unsigned short m_prevBinning;
56     void *m_buffer;
57     size_t m_buffer_size;
58     wxByte m_bpp;  // bits per pixel: 8 or 16
59     CaptureMode m_mode;
60     bool m_capturing;
61     int m_cameraId;
62     int m_minGain;
63     int m_maxGain;
64     int m_defaultGainPct;
65     bool m_isColor;
66     double m_devicePixelSize;
67 
68 public:
69     SVBCamera();
70     ~SVBCamera();
71 
CanSelectCamera() const72     bool CanSelectCamera() const override { return true; }
73     bool EnumCameras(wxArrayString& names, wxArrayString& ids) override;
74     bool Capture(int duration, usImage& img, int options, const wxRect& subframe) override;
75     bool Connect(const wxString& camId) override;
76     bool Disconnect() override;
77 
78     bool ST4PulseGuideScope(int direction, int duration) override;
79 
80     void ShowPropertyDialog() override;
HasNonGuiCapture()81     bool HasNonGuiCapture() override { return true; }
ST4HasNonGuiMove()82     bool ST4HasNonGuiMove() override { return true; }
83     wxByte BitsPerPixel() override;
84     bool GetDevicePixelSize(double *devPixelSize) override;
85     int GetDefaultCameraGain() override;
86 
87 private:
88     void StopCapture();
89     bool StopExposure();
90 };
91 
SVBCamera()92 SVBCamera::SVBCamera()
93     :
94     m_buffer(nullptr)
95 {
96     Name = _T("Svbony Camera");
97     PropertyDialogType = PROPDLG_WHEN_DISCONNECTED;
98     Connected = false;
99     m_hasGuideOutput = false; // updated when connected
100     HasSubframes = true;
101     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
102     m_defaultGainPct = GuideCamera::GetDefaultCameraGain();
103     int value = pConfig->Profile.GetInt("/camera/svb/bpp", 16);
104     m_bpp = value == 8 ? 8 : 16;
105 }
106 
~SVBCamera()107 SVBCamera::~SVBCamera()
108 {
109     ::free(m_buffer);
110 }
111 
BitsPerPixel()112 wxByte SVBCamera::BitsPerPixel()
113 {
114     return m_bpp;
115 }
116 
117 struct SVBCameraDlg : public wxDialog
118 {
119     wxRadioButton *m_bpp8;
120     wxRadioButton *m_bpp16;
121     SVBCameraDlg();
122 };
123 
SVBCameraDlg()124 SVBCameraDlg::SVBCameraDlg()
125     : wxDialog(wxGetApp().GetTopWindow(), wxID_ANY, _("Svbony Camera Properties"))
126 {
127     SetSizeHints(wxDefaultSize, wxDefaultSize);
128 
129     wxBoxSizer *bSizer12 = new wxBoxSizer(wxVERTICAL);
130     wxStaticBoxSizer *sbSizer3 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Camera Mode")), wxHORIZONTAL);
131 
132     m_bpp8 = new wxRadioButton(this, wxID_ANY, _("8-bit"));
133     m_bpp16 = new wxRadioButton(this, wxID_ANY, _("16-bit"));
134     sbSizer3->Add(m_bpp8, 0, wxALL, 5);
135     sbSizer3->Add(m_bpp16, 0, wxALL, 5);
136     bSizer12->Add(sbSizer3, 1, wxEXPAND, 5);
137 
138     wxStdDialogButtonSizer *sdbSizer2 = new wxStdDialogButtonSizer();
139     wxButton *sdbSizer2OK = new wxButton(this, wxID_OK);
140     wxButton* sdbSizer2Cancel = new wxButton(this, wxID_CANCEL);
141     sdbSizer2->AddButton(sdbSizer2OK);
142     sdbSizer2->AddButton(sdbSizer2Cancel);
143     sdbSizer2->Realize();
144     bSizer12->Add(sdbSizer2, 0, wxALL | wxEXPAND, 5);
145 
146     SetSizer(bSizer12);
147     Layout();
148     Fit();
149 
150     Centre(wxBOTH);
151 }
152 
ShowPropertyDialog()153 void SVBCamera::ShowPropertyDialog()
154 {
155     SVBCameraDlg dlg;
156     int value = pConfig->Profile.GetInt("/camera/svb/bpp", m_bpp);
157     if (value == 8)
158         dlg.m_bpp8->SetValue(true);
159     else
160         dlg.m_bpp16->SetValue(true);
161     if (dlg.ShowModal() == wxID_OK)
162     {
163         m_bpp = dlg.m_bpp8->GetValue() ? 8 : 16;
164         pConfig->Profile.SetInt("/camera/svb/bpp", m_bpp);
165     }
166 }
167 
cam_gain(int minval,int maxval,int pct)168 inline static int cam_gain(int minval, int maxval, int pct)
169 {
170     return minval + pct * (maxval - minval) / 100;
171 }
172 
gain_pct(int minval,int maxval,int val)173 inline static int gain_pct(int minval, int maxval, int val)
174 {
175     return (val - minval) * 100 / (maxval - minval);
176 }
177 
TryLoadDll(wxString * err)178 static bool TryLoadDll(wxString *err)
179 {
180     static bool s_logged;
181     if (!s_logged)
182     {
183         const char *ver = SVBGetSDKVersion();
184         Debug.Write(wxString::Format("SVB: SDK Version = [%s]\n", ver));
185         s_logged = true;
186     }
187 
188     return true;
189 }
190 
EnumCameras(wxArrayString & names,wxArrayString & ids)191 bool SVBCamera::EnumCameras(wxArrayString& names, wxArrayString& ids)
192 {
193     wxString err;
194     if (!TryLoadDll(&err))
195     {
196         wxMessageBox(err, _("Error"), wxOK | wxICON_ERROR);
197         return true;
198     }
199 
200     // Find available cameras
201     int numCameras = SVBGetNumOfConnectedCameras();
202 
203     for (int i = 0; i < numCameras; i++)
204     {
205         SVB_CAMERA_INFO info;
206         if (SVBGetCameraInfo(&info, i) == SVB_SUCCESS)
207         {
208             if (numCameras > 1)
209                 names.Add(wxString::Format("%d: %s S/N %s", i + 1, info.FriendlyName, info.CameraSN));
210             else
211                 names.Add(wxString::Format("%s S/N %s", info.FriendlyName, info.CameraSN));
212             ids.Add(info.CameraSN);
213         }
214     }
215 
216     return false;
217 }
218 
FindCamera(const wxString & camId,wxString * err)219 static int FindCamera(const wxString& camId, wxString *err)
220 {
221     int numCameras = SVBGetNumOfConnectedCameras();
222 
223     Debug.Write(wxString::Format("SVB: find camera id: [%s], ncams = %d\n", camId, numCameras));
224 
225     if (numCameras <= 0)
226     {
227         *err = _("No Svbony cameras detected.");
228         return -1;
229     }
230 
231     if (camId == GuideCamera::DEFAULT_CAMERA_ID)
232     {
233         // camera id specified, connect to the first camera
234         return 0;
235     }
236 
237     // find the camera with the matching serial number
238 
239     for (int i = 0; i < numCameras; i++)
240     {
241         SVB_CAMERA_INFO info;
242         if (SVBGetCameraInfo(&info, i) == SVB_SUCCESS)
243         {
244             Debug.Write(wxString::Format("SVB: cam [%d] id %d %s S/N %s\n", i, info.CameraID, info.FriendlyName, info.CameraSN));
245             if (info.CameraSN == camId)
246             {
247                 Debug.Write(wxString::Format("SVB: found matching camera at idx %d, id=%d\n", i, info.CameraID));
248                 return i;
249             }
250         }
251     }
252 
253     Debug.Write("SVB: no matching cameras\n");
254     *err = wxString::Format(_("Camera %s not found"), camId);
255     return -1;
256 }
257 
ImgTypeBits(SVB_IMG_TYPE t)258 inline static int ImgTypeBits(SVB_IMG_TYPE t)
259 {
260     switch (t) {
261     case SVB_IMG_RAW8: return 8;
262     case SVB_IMG_RAW10: return 10;
263     case SVB_IMG_RAW12: return 12;
264     case SVB_IMG_RAW14: return 14;
265     case SVB_IMG_RAW16: return 16;
266     default: return -1;
267     }
268 }
269 
Connect(const wxString & camId)270 bool SVBCamera::Connect(const wxString& camId)
271 {
272     wxString err;
273     if (!TryLoadDll(&err))
274     {
275         return CamConnectFailed(err);
276     }
277 
278     int selected = FindCamera(camId, &err);
279     if (selected == -1)
280     {
281         return CamConnectFailed(err);
282     }
283 
284     SVB_ERROR_CODE r;
285     SVB_CAMERA_INFO info;
286     if ((r = SVBGetCameraInfo(&info, selected)) != SVB_SUCCESS)
287     {
288         Debug.Write(wxString::Format("SVBGetCameraInfo ret %d\n", r));
289         return CamConnectFailed(_("Failed to get camera info for Svbony camera."));
290     }
291 
292     m_cameraId = info.CameraID;
293 
294     if ((r = SVBOpenCamera(m_cameraId)) != SVB_SUCCESS)
295     {
296         Debug.Write(wxString::Format("SVBOpenCamera ret %d\n", r));
297         return CamConnectFailed(_("Failed to open Svbony camera."));
298     }
299 
300     SVB_CAMERA_PROPERTY props;
301     if ((r = SVBGetCameraProperty(m_cameraId, &props)) != SVB_SUCCESS)
302     {
303         Disconnect();
304         Debug.Write(wxString::Format("SVBGetCameraProperty ret %d\n", r));
305         return CamConnectFailed(_("Failed to get camera properties for Svbony camera."));
306     }
307 
308     Debug.Write(wxString::Format("SVB: name = [%s] SN = [%s]\n", info.FriendlyName, info.CameraSN));
309 
310     // find the best image type matching our bpp selection
311     SVB_IMG_TYPE img_type = SVB_IMG_END;
312     int maxbits = -1;
313     for (int i = 0; i < sizeof(props.SupportedVideoFormat) / sizeof(props.SupportedVideoFormat[0]); i++)
314     {
315         if (m_bpp == 8)
316         {
317             if (props.SupportedVideoFormat[i] == SVB_IMG_RAW8)
318             {
319                 img_type = props.SupportedVideoFormat[i];
320                 break;
321             }
322         }
323         else
324         {
325             int bits = ImgTypeBits(props.SupportedVideoFormat[i]);
326             if (bits > 8 && bits <= 16 && bits > maxbits)
327             {
328                 maxbits = bits;
329                 img_type = props.SupportedVideoFormat[i];
330             }
331         }
332     }
333 
334     Debug.Write(wxString::Format("SVB: using mode BPP = %u, image type %d\n", (unsigned int)m_bpp, img_type));
335 
336     if (img_type == SVB_IMG_END)
337     {
338         Disconnect();
339         return CamConnectFailed(wxString::Format(_("The camera does not support %s mode, try selecting %s mode"),
340             m_bpp == 8 ? _("8-bit") : _("16-bit"),
341             m_bpp == 8 ? _("16-bit") : _("8-bit")));
342     }
343 
344     m_mode = CM_VIDEO;
345 
346     if (props.IsTriggerCam)
347     {
348         SVB_SUPPORTED_MODE sm;
349         SVBGetCameraSupportMode(m_cameraId, &sm);
350         for (int i = 0; i < sizeof(sm.SupportedCameraMode) / sizeof(sm.SupportedCameraMode[0]); i++)
351         {
352             if (sm.SupportedCameraMode[i] == SVB_MODE_END)
353                 break;
354             if (sm.SupportedCameraMode[i] == SVB_MODE_TRIG_SOFT)
355             {
356                 m_mode = CM_SNAP;
357                 break;
358             }
359         }
360     }
361 
362     if (m_mode == CM_SNAP)
363     {
364         Debug.Write("SVB: selecting trigger mode\n");
365         if ((r = SVBSetCameraMode(m_cameraId, SVB_MODE_TRIG_SOFT)) != SVB_SUCCESS)
366         {
367             Debug.Write(wxString::Format("SVBSetCameraMode(SVB_MODE_TRIG_SOFT) ret %d\n", r));
368             // fall-back to video mode
369             m_mode = CM_VIDEO;
370         }
371     }
372     if (m_mode == CM_VIDEO)
373     {
374         Debug.Write("SVB: selecting video mode\n");
375         if ((r = SVBSetCameraMode(m_cameraId, SVB_MODE_NORMAL)) != SVB_SUCCESS)
376         {
377             Debug.Write(wxString::Format("SVBSetCameraMode(SVB_MODE_NORMAL) ret %d\n", r));
378             Disconnect();
379             return CamConnectFailed(_("Unable to initialize camera."));
380         }
381     }
382 
383     Connected = true;
384     Name = info.FriendlyName;
385     m_isColor = props.IsColorCam != SVB_FALSE;
386     Debug.Write(wxString::Format("SVB: IsColorCam = %d\n", m_isColor));
387 
388     HasShutter = false;
389 
390     int maxBin = 1;
391     for (int i = 0; i <= WXSIZEOF(props.SupportedBins); i++)
392     {
393         if (!props.SupportedBins[i])
394             break;
395         Debug.Write(wxString::Format("SVB: supported bin %d = %d\n", i, props.SupportedBins[i]));
396         if (props.SupportedBins[i] > maxBin)
397             maxBin = props.SupportedBins[i];
398     }
399     MaxBinning = maxBin;
400 
401     if (Binning > MaxBinning)
402         Binning = MaxBinning;
403 
404     m_maxSize.x = props.MaxWidth;
405     m_maxSize.y = props.MaxHeight;
406 
407     FullSize.x = m_maxSize.x / Binning;
408     FullSize.y = m_maxSize.y / Binning;
409     m_prevBinning = Binning;
410 
411     ::free(m_buffer);
412     m_buffer_size = props.MaxWidth * props.MaxHeight * (m_bpp == 8 ? 1 : 2);
413     m_buffer = ::malloc(m_buffer_size);
414 
415     float pxsize;
416     if ((r = SVBGetSensorPixelSize(m_cameraId, &pxsize)) == SVB_SUCCESS)
417         m_devicePixelSize = pxsize;
418     else
419         Debug.Write(wxString::Format("SVBGetSensorPixelSize ret %d\n", r));
420 
421     SVBStopVideoCapture(m_cameraId);
422     m_capturing = false;
423 
424     int numControls;
425     if ((r = SVBGetNumOfControls(m_cameraId, &numControls)) != SVB_SUCCESS)
426     {
427         Debug.Write(wxString::Format("SVBGetNumOfControls ret %d\n", r));
428         Disconnect();
429         return CamConnectFailed(_("Failed to get camera properties for Svbony camera."));
430     }
431 
432     SVB_BOOL cpg;
433     if ((r = SVBCanPulseGuide(m_cameraId, &cpg)) == SVB_SUCCESS)
434     {
435         m_hasGuideOutput = cpg != SVB_FALSE;
436         Debug.Write(wxString::Format("SVBCanPulseGuide: %s\n", m_hasGuideOutput ? "yes" : "no"));
437     }
438     else
439     {
440         Debug.Write(wxString::Format("SVBCanPulseGuide ret %d, assuming no ST4 output\n", r));
441     }
442 
443     HasGainControl = false;
444     HasCooler = false;
445 
446     for (int i = 0; i < numControls; i++)
447     {
448         SVB_CONTROL_CAPS caps;
449         if (SVBGetControlCaps(m_cameraId, i, &caps) == SVB_SUCCESS)
450         {
451             switch (caps.ControlType)
452             {
453             case SVB_GAIN:
454                 if (caps.IsWritable)
455                 {
456                     HasGainControl = true;
457                     m_minGain = caps.MinValue;
458                     m_maxGain = caps.MaxValue;
459                     m_defaultGainPct = gain_pct(m_minGain, m_maxGain, caps.DefaultValue);
460                     Debug.Write(wxString::Format("SVB: gain range = %d .. %d default = %ld (%d%%)\n",
461                         m_minGain, m_maxGain, caps.DefaultValue, m_defaultGainPct));
462                 }
463                 // fall through
464 
465             // set everything to default and no auto
466             case SVB_EXPOSURE:
467             case SVB_GAMMA:
468             case SVB_GAMMA_CONTRAST:
469             case SVB_WB_R:
470             case SVB_WB_G:
471             case SVB_WB_B:
472             case SVB_FLIP: //reference: enum SVB_FLIP_STATUS
473             case SVB_FRAME_SPEED_MODE: // 0:low speed, 1:medium speed, 2:high speed
474             case SVB_CONTRAST:
475             case SVB_SHARPNESS:
476             case SVB_SATURATION:
477             case SVB_AUTO_TARGET_BRIGHTNESS:
478             case SVB_BLACK_LEVEL: //black level offset
479                 SVBSetControlValue(m_cameraId, caps.ControlType, caps.DefaultValue, SVB_FALSE);
480                 break;
481 
482             default:
483                 break;
484             }
485         }
486 
487     }
488 
489     m_frame = wxRect(FullSize);
490     Debug.Write(wxString::Format("SVB: frame (%d,%d)+(%d,%d)\n", m_frame.x, m_frame.y, m_frame.width, m_frame.height));
491 
492     SVBSetOutputImageType(m_cameraId, img_type);
493 
494     SVBSetROIFormat(m_cameraId, m_frame.GetLeft(), m_frame.GetTop(),
495         m_frame.GetWidth(), m_frame.GetHeight(), Binning);
496 
497     return false;
498 }
499 
StopCapture()500 void SVBCamera::StopCapture()
501 {
502     if (m_capturing)
503     {
504         Debug.Write("SVB: stopcapture\n");
505         SVBStopVideoCapture(m_cameraId);
506         m_capturing = false;
507     }
508 }
509 
Disconnect()510 bool SVBCamera::Disconnect()
511 {
512     StopCapture();
513     SVBCloseCamera(m_cameraId);
514 
515     Connected = false;
516 
517     ::free(m_buffer);
518     m_buffer = nullptr;
519 
520     return false;
521 }
522 
StopExposure()523 bool SVBCamera::StopExposure()
524 {
525     Debug.Write("SVB: stopexposure\n");
526     // FIXME - TODO
527     return true;
528 }
529 
GetDevicePixelSize(double * devPixelSize)530 bool SVBCamera::GetDevicePixelSize(double *devPixelSize)
531 {
532     if (!Connected)
533         return true;
534 
535     *devPixelSize = m_devicePixelSize;
536     return false;
537 }
538 
GetDefaultCameraGain()539 int SVBCamera::GetDefaultCameraGain()
540 {
541     return m_defaultGainPct;
542 }
543 
round_down(int v,int m)544 inline static int round_down(int v, int m)
545 {
546     return v & ~(m - 1);
547 }
548 
round_up(int v,int m)549 inline static int round_up(int v, int m)
550 {
551     return round_down(v + m - 1, m);
552 }
553 
flush_buffered_image(int cameraId,void * buf,size_t size)554 static void flush_buffered_image(int cameraId, void *buf, size_t size)
555 {
556     enum { NUM_IMAGE_BUFFERS = 2 }; // camera has 2 internal frame buffers
557 
558     // clear buffered frames if any
559 
560     for (unsigned int num_cleared = 0; num_cleared < NUM_IMAGE_BUFFERS; num_cleared++)
561     {
562         SVB_ERROR_CODE status = SVBGetVideoData(cameraId, (unsigned char *) buf, size, 0);
563         if (status != SVB_SUCCESS)
564             break; // no more buffered frames
565 
566         Debug.Write(wxString::Format("SVB: getimagedata clearbuf %u ret %d\n", num_cleared + 1, status));
567     }
568 }
569 
Capture(int duration,usImage & img,int options,const wxRect & subframe)570 bool SVBCamera::Capture(int duration, usImage& img, int options, const wxRect& subframe)
571 {
572     bool binning_change = false;
573     if (Binning != m_prevBinning)
574     {
575         FullSize.x = m_maxSize.x / Binning;
576         FullSize.y = m_maxSize.y / Binning;
577         m_prevBinning = Binning;
578         binning_change = true;
579     }
580 
581     if (img.Init(FullSize))
582     {
583         DisconnectWithAlert(CAPT_FAIL_MEMORY);
584         return true;
585     }
586 
587     wxRect frame;
588     wxPoint subframePos; // position of subframe within frame
589 
590     bool useSubframe = UseSubframes;
591 
592     if (subframe.width <= 0 || subframe.height <= 0)
593         useSubframe = false;
594 
595     if (useSubframe)
596     {
597         // ensure transfer size is a multiple of 1024
598         //  moving the sub-frame or resizing it is somewhat costly (stopCapture / startCapture)
599 
600         frame.SetLeft(round_down(subframe.GetLeft(), 32));
601         frame.SetRight(round_up(subframe.GetRight() + 1, 32) - 1);
602         frame.SetTop(round_down(subframe.GetTop(), 32));
603         frame.SetBottom(round_up(subframe.GetBottom() + 1, 32) - 1);
604 
605         subframePos = subframe.GetLeftTop() - frame.GetLeftTop();
606     }
607     else
608     {
609         frame = wxRect(FullSize);
610     }
611 
612     SVB_BOOL tmp;
613     long cur_exp;
614     // The returned exposure value may differ from the requested exposure by several usecs,
615     // so round the returned exposure to the nearest millisecond.
616     if (SVBGetControlValue(m_cameraId, SVB_EXPOSURE, &cur_exp, &tmp) == SVB_SUCCESS &&
617         (cur_exp + 500) / 1000 != duration)
618     {
619         Debug.Write(wxString::Format("SVB: set CONTROL_EXPOSURE %d\n", duration * 1000));
620         SVBSetControlValue(m_cameraId, SVB_EXPOSURE, duration * 1000, SVB_FALSE);
621     }
622 
623     long new_gain = cam_gain(m_minGain, m_maxGain, GuideCameraGain);
624     long cur_gain;
625     if (SVBGetControlValue(m_cameraId, SVB_GAIN, &cur_gain, &tmp) == SVB_SUCCESS &&
626         new_gain != cur_gain)
627     {
628         Debug.Write(wxString::Format("SVB: set CONTROL_GAIN %d%% %d\n", GuideCameraGain, new_gain));
629         SVBSetControlValue(m_cameraId, SVB_GAIN, new_gain, SVB_FALSE);
630     }
631 
632     bool size_change = frame.GetSize() != m_frame.GetSize();
633     bool pos_change = frame.GetLeftTop() != m_frame.GetLeftTop();
634 
635     if (size_change || pos_change)
636     {
637         m_frame = frame;
638         Debug.Write(wxString::Format("SVB: frame (%d,%d)+(%d,%d)\n", m_frame.x, m_frame.y, m_frame.width, m_frame.height));
639     }
640 
641     if (pos_change || size_change || binning_change)
642     {
643         StopCapture();
644 
645         SVB_ERROR_CODE status = SVBSetROIFormat(m_cameraId, frame.GetLeft(), frame.GetTop(),
646             frame.GetWidth(), frame.GetHeight(), Binning);
647 
648         if (status != SVB_SUCCESS)
649             Debug.Write(wxString::Format("SVB: setImageFormat(%d,%d,%d,%d,%hu) => %d\n",
650                 frame.GetLeft(), frame.GetTop(), frame.GetWidth(), frame.GetHeight(), Binning, status));
651     }
652 
653     int poll = wxMin(duration, 100);
654 
655     unsigned char *const buffer =
656         m_bpp == 16 && !useSubframe ? (unsigned char *) img.ImageData : (unsigned char *) m_buffer;
657 
658     if (m_mode == CM_VIDEO)
659     {
660         // the camera and/or driver will buffer frames and return the oldest frame,
661         // which could be quite stale. read out all buffered frames so the frame we
662         // get is current
663 
664         flush_buffered_image(m_cameraId, m_buffer, m_buffer_size);
665 
666         if (!m_capturing)
667         {
668             Debug.Write("SVB: startcapture\n");
669             SVBStartVideoCapture(m_cameraId);
670             m_capturing = true;
671         }
672 
673         CameraWatchdog watchdog(duration, duration + GetTimeoutMs() + 10000); // total timeout is 2 * duration + 15s (typically)
674 
675         while (true)
676         {
677             SVB_ERROR_CODE status = SVBGetVideoData(m_cameraId, buffer, m_buffer_size, poll);
678             if (status == SVB_SUCCESS)
679                 break;
680             if (WorkerThread::InterruptRequested())
681             {
682                 StopCapture();
683                 return true;
684             }
685             if (watchdog.Expired())
686             {
687                 Debug.Write(wxString::Format("SVB: getimagedata ret %d\n", status));
688                 StopCapture();
689                 DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
690                 return true;
691             }
692         }
693     }
694     else
695     {
696         // CM_SNAP
697 
698         bool frame_ready = false;
699 
700         if (!m_capturing)
701         {
702             Debug.Write("SVB: startcapture\n");
703             SVBStartVideoCapture(m_cameraId);
704             m_capturing = true;
705         }
706 
707         for (int tries = 1; tries <= 3 && !frame_ready; tries++)
708         {
709             if (tries > 1)
710                 Debug.Write("SVB: getexpstatus EXP_FAILED, retry exposure\n");
711 
712             SVBSendSoftTrigger(m_cameraId);
713 
714             enum { GRACE_PERIOD_MS = 250 };
715             CameraWatchdog watchdog(duration, duration + GRACE_PERIOD_MS);
716 
717             if (duration > 100)
718             {
719                 // wait until near end of exposure
720                 if (WorkerThread::MilliSleep(duration - 100, WorkerThread::INT_ANY) &&
721                     (WorkerThread::TerminateRequested() || StopExposure()))
722                 {
723                     StopExposure();
724                     return true;
725                 }
726             }
727 
728             while (true)
729             {
730                 SVB_ERROR_CODE status = SVBGetVideoData(m_cameraId, buffer, m_buffer_size, poll);
731                 if (status == SVB_SUCCESS)
732                 {
733                     frame_ready = true;
734                     break;
735                 }
736                 if (WorkerThread::InterruptRequested())
737                 {
738                     StopCapture();
739                     return true;
740                 }
741                 if (watchdog.Expired())
742                 {
743                     // exposure timed-out, retry
744                     Debug.Write("SVB: exposure timed-out, retry\n");
745                     break;
746                 }
747             }
748         }
749 
750         if (!frame_ready)
751         {
752             Debug.Write("SVB: exposure failed, giving up\n");
753             DisconnectWithAlert(_("Lost connection to camera"), RECONNECT);
754             return true;
755         }
756     }
757 
758     if (useSubframe)
759     {
760         img.Subframe = subframe;
761 
762         // Clear out the image
763         img.Clear();
764 
765         if (m_bpp == 8)
766         {
767             for (int y = 0; y < subframe.height; y++)
768             {
769                 const unsigned char *src = buffer + (y + subframePos.y) * frame.width + subframePos.x;
770                 unsigned short *dst = img.ImageData + (y + subframe.y) * FullSize.GetWidth() + subframe.x;
771                 for (int x = 0; x < subframe.width; x++)
772                     *dst++ = *src++;
773             }
774         }
775         else
776         {
777             for (int y = 0; y < subframe.height; y++)
778             {
779                 const unsigned short *src = (unsigned short *) buffer + (y + subframePos.y) * frame.width + subframePos.x;
780                 unsigned short *dst = img.ImageData + (y + subframe.y) * FullSize.GetWidth() + subframe.x;
781                 for (int x = 0; x < subframe.width; x++)
782                     *dst++ = *src++;
783             }
784         }
785     }
786     else
787     {
788         if (m_bpp == 8)
789         {
790             for (unsigned int i = 0; i < img.NPixels; i++)
791                 img.ImageData[i] = buffer[i];
792         }
793         else
794         {
795             // 16-bit mode and no subframe: data is already in img.ImageData
796         }
797     }
798 
799     if (options & CAPTURE_SUBTRACT_DARK)
800         SubtractDark(img);
801     if (m_isColor && Binning == 1 && (options & CAPTURE_RECON))
802        QuickLRecon(img);
803 
804     return false;
805 }
806 
GetSVBDirection(int direction)807 inline static SVB_GUIDE_DIRECTION GetSVBDirection(int direction)
808 {
809     switch (direction)
810     {
811     default:
812     case NORTH:
813         return SVB_GUIDE_NORTH;
814     case SOUTH:
815         return SVB_GUIDE_SOUTH;
816     case EAST:
817         return SVB_GUIDE_EAST;
818     case WEST:
819         return SVB_GUIDE_WEST;
820     }
821 }
822 
ST4PulseGuideScope(int direction,int duration)823 bool SVBCamera::ST4PulseGuideScope(int direction, int duration)
824 {
825     SVB_GUIDE_DIRECTION d = GetSVBDirection(direction);
826     SVBPulseGuide(m_cameraId, d, duration);
827     return false;
828 }
829 
MakeSVBCamera()830 GuideCamera *SVBCameraFactory::MakeSVBCamera()
831 {
832     return new SVBCamera();
833 }
834 
835 #endif // SVB_CAMERA
836