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