1 #if 0
2 V4L INDI Driver
3 INDI Interface for V4L devices
4 Copyright (C) 2003 - 2013 Jasem Mutlaq (mutlaqja@ikarustech.com)
5 
6     This library is free software;
7 you can redistribute it and / or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation;
10 either
11 version 2.1 of the License, or (at your option) any later version.
12 
13 This library is distributed in the hope that it will be useful,
14      but WITHOUT ANY WARRANTY;
15 without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 Lesser General Public License for more details.
18 
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library;
21 if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110 - 1301  USA
23 
24 #endif
25 
26 #include <cmath>
27 #include <algorithm>
28 #include "v4l2driver.h"
29 #include "indistandardproperty.h"
30 #include "lx/Lx.h"
31 
32 // Pixel size info for different cameras
33 typedef struct
34 {
35     const char * deviceName; // device name reported by V4L
36     const char * commonName; // if null, use device name
37     float pixelSizeX;
38     float pixelSizeY; // if negative, use pixelSizeX also for Y
39     bool tested; //if False print please report message
40 } PixelSizeInfo;
41 
42 static const PixelSizeInfo pixelSizeInfo[] =
43 {
44     { "NexImage 5", nullptr, 2.2f, -1, true },
45     { "UVC Camera (046d:0809)", "Logitech Webcam Pro 9000", 3.3f, -1, true },
46     { "SVBONY SV105: SVBONY SV105", "SVBONY SV105", 3.0f, -1, true },
47     { "SVBONY SV205: SVBONY SV205", "SVBONY SV205", 4.0f, -1, true },
48     { "NexImage 10", nullptr, 1.67f, -1, true },
49     { "NexImage Burst Color", nullptr, 3.75f, -1, false },
50     { "NexImage Burst Mono", nullptr, 3.75f, -1, false },
51     { "Skyris 132C", nullptr, 3.75f, -1, false },
52     { "Skyris 132M", nullptr, 3.75f, -1, false },
53     { "Skyris 236C", nullptr, 2.8f, -1, false },
54     { "Skyris 236M", nullptr, 2.8f, -1, false },
55     { "iOptron iPolar: iOptron iPolar", nullptr, 3.75f, -1, true },
56     { "iOptron iGuider: iOptron iGuide", nullptr, 3.75f, -1, true },
57     { "mmal service 16.1", "Raspberry Pi High Quality Camera", 1.55f, -1, true },
58     { "UVC Camera (046d:0825)", "Logitech HD C270", 2.8f, -1, true },
59     { "0c45:6366 Microdia", "Spinel 2MP Full HD Low Light WDR H264 USB Camera Module IMX290", 2.9f, -1, true },
60     { "Microsoft® LifeCam Cinema(TM):", "Microsoft® LifeCam Cinema(TM)", 3.0f, -1, false },
61     { nullptr, nullptr, 5.6f, -1, false}  // sentinel and default pixel size, needs to be last
62 };
63 
V4L2_Driver()64 V4L2_Driver::V4L2_Driver()
65 {
66     setVersion(1, 0);
67 
68     allocateBuffers();
69 
70     divider = 128.;
71 
72     is_capturing = false;
73     non_capture_frames = 0;
74 
75     Options          = nullptr;
76     v4loptions       = 0;
77     AbsExposureN     = nullptr;
78     ManualExposureSP = nullptr;
79     CaptureSizesSP.sp = nullptr;
80     CaptureSizesNP.np = nullptr;
81     FrameRatesSP.sp = nullptr;
82 
83     frame_received.tv_sec = 0;
84     frame_received.tv_usec = 0;
85 
86     v4l_capture_started = false;
87 
88     stackMode = STACK_NONE;
89 
90     lx       = new Lx();
91     lxtimer  = -1;
92     stdtimer = -1;
93 }
94 
~V4L2_Driver()95 V4L2_Driver::~V4L2_Driver()
96 {
97     releaseBuffers();
98 }
99 
updateFrameSize()100 void V4L2_Driver::updateFrameSize()
101 {
102     if (ISS_ON == ImageColorS[IMAGE_GRAYSCALE].s)
103         frameBytes =
104             PrimaryCCD.getSubW() * PrimaryCCD.getSubH() * (PrimaryCCD.getBPP() / 8 + (PrimaryCCD.getBPP() % 8 ? 1 : 0));
105     else
106         frameBytes = PrimaryCCD.getSubW() * PrimaryCCD.getSubH() *
107                      (PrimaryCCD.getBPP() / 8 + (PrimaryCCD.getBPP() % 8 ? 1 : 0)) * 3;
108 
109     PrimaryCCD.setFrameBufferSize(frameBytes);
110     LOGF_DEBUG("%s: frame bytes %d", __FUNCTION__, PrimaryCCD.getFrameBufferSize());
111 }
112 
initProperties()113 bool V4L2_Driver::initProperties()
114 {
115     INDI::CCD::initProperties();
116     addDebugControl();
117 
118     /* Port */
119     char configPort[256] = {0};
120     if (IUGetConfigText(getDeviceName(), PortTP.name, PortT[0].name, configPort, 256) == 0)
121         IUFillText(&PortT[0], "PORT", "Port", configPort);
122     else
123         IUFillText(&PortT[0], "PORT", "Port", "/dev/video0");
124     IUFillTextVector(&PortTP, PortT, NARRAY(PortT), getDeviceName(), INDI::SP::DEVICE_PORT, "Ports", OPTIONS_TAB, IP_RW, 0,
125                      IPS_IDLE);
126 
127     /* Color space */
128     IUFillSwitch(&ImageColorS[IMAGE_GRAYSCALE], "CCD_COLOR_GRAY", "Gray", ISS_ON);
129     IUFillSwitch(&ImageColorS[1], "CCD_COLOR_RGB", "Color", ISS_OFF);
130     IUFillSwitchVector(&ImageColorSP, ImageColorS, NARRAY(ImageColorS), getDeviceName(), "CCD_COLOR_SPACE",
131                        "Image Type", IMAGE_SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
132 
133     /* Image depth */
134     IUFillSwitch(&ImageDepthS[0], "8 bits", "", ISS_ON);
135     IUFillSwitch(&ImageDepthS[1], "16 bits", "", ISS_OFF);
136     IUFillSwitchVector(&ImageDepthSP, ImageDepthS, NARRAY(ImageDepthS), getDeviceName(), "Image Depth", "",
137                        IMAGE_SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
138 
139     /* Camera Name */
140     IUFillText(&camNameT[0], "Model", "", nullptr);
141     IUFillTextVector(&camNameTP, camNameT, NARRAY(camNameT), getDeviceName(), "Camera", "", IMAGE_INFO_TAB, IP_RO, 0,
142                      IPS_IDLE);
143 
144     /* Stacking Mode */
145     IUFillSwitch(&StackModeS[STACK_NONE], "None", "", ISS_ON);
146     IUFillSwitch(&StackModeS[STACK_MEAN], "Mean", "", ISS_OFF);
147     IUFillSwitch(&StackModeS[STACK_ADDITIVE], "Additive", "", ISS_OFF);
148     IUFillSwitch(&StackModeS[STACK_TAKE_DARK], "Take Dark", "", ISS_OFF);
149     IUFillSwitch(&StackModeS[STACK_RESET_DARK], "Reset Dark", "", ISS_OFF);
150     IUFillSwitchVector(&StackModeSP, StackModeS, NARRAY(StackModeS), getDeviceName(), "Stack", "", MAIN_CONTROL_TAB,
151                        IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
152 
153     stackMode = STACK_NONE;
154 
155     /* Inputs */
156     IUFillSwitchVector(&InputsSP, nullptr, 0, getDeviceName(), "V4L2_INPUT", "Inputs", CAPTURE_FORMAT, IP_RW,
157                        ISR_1OFMANY, 0, IPS_IDLE);
158     /* Capture Formats */
159     IUFillSwitchVector(&CaptureFormatsSP, nullptr, 0, getDeviceName(), "V4L2_FORMAT", "Capture Format", CAPTURE_FORMAT,
160                        IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
161     /* Capture Sizes */
162     IUFillSwitchVector(&CaptureSizesSP, nullptr, 0, getDeviceName(), "V4L2_SIZE_DISCRETE", "Capture Size",
163                        CAPTURE_FORMAT, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
164     IUFillNumberVector(&CaptureSizesNP, nullptr, 0, getDeviceName(), "V4L2_SIZE_STEP", "Capture Size", CAPTURE_FORMAT,
165                        IP_RW, 0, IPS_IDLE);
166     /* Frame Rate */
167     IUFillSwitchVector(&FrameRatesSP, nullptr, 0, getDeviceName(), "V4L2_FRAMEINT_DISCRETE", "Frame Interval",
168                        CAPTURE_FORMAT, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
169     IUFillNumberVector(&FrameRateNP, nullptr, 0, getDeviceName(), "V4L2_FRAMEINT_STEP", "Frame Interval",
170                        CAPTURE_FORMAT, IP_RW, 60, IPS_IDLE);
171     /* Capture Colorspace */
172     IUFillText(&CaptureColorSpaceT[0], "Name", "", nullptr);
173     IUFillText(&CaptureColorSpaceT[1], "YCbCr Encoding", "", nullptr);
174     IUFillText(&CaptureColorSpaceT[2], "Quantization", "", nullptr);
175     IUFillTextVector(&CaptureColorSpaceTP, CaptureColorSpaceT, NARRAY(CaptureColorSpaceT), getDeviceName(),
176                      "V4L2_COLORSPACE", "ColorSpace", IMAGE_INFO_TAB, IP_RO, 0, IPS_IDLE);
177 
178     /* Color Processing */
179     IUFillSwitch(&ColorProcessingS[0], "Quantization", "", ISS_ON);
180     IUFillSwitch(&ColorProcessingS[1], "Color Conversion", "", ISS_OFF);
181     IUFillSwitch(&ColorProcessingS[2], "Linearization", "", ISS_OFF);
182     IUFillSwitchVector(&ColorProcessingSP, ColorProcessingS, NARRAY(ColorProcessingS), getDeviceName(),
183                        "V4L2_COLOR_PROCESSING", "Color Process", CAPTURE_FORMAT, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
184 
185     /* V4L2 Settings */
186     IUFillNumberVector(&ImageAdjustNP, nullptr, 0, getDeviceName(), "Image Adjustments", "", IMAGE_GROUP, IP_RW, 60,
187                        IPS_IDLE);
188 
189     PrimaryCCD.getCCDInfo()->p = IP_RW;
190 
191     PrimaryCCD.setMinMaxStep("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", 0.001, 3600, 1, false);
192 
193     if (!lx->initProperties(this))
194         LOG_WARN("Can not init Long Exposure");
195 
196 #ifdef HAVE_WEBSOCKET
197     SetCCDCapability(CCD_CAN_BIN | CCD_CAN_SUBFRAME | CCD_HAS_STREAMING | CCD_CAN_ABORT | CCD_HAS_WEB_SOCKET);
198 #else
199     SetCCDCapability(CCD_CAN_BIN | CCD_CAN_SUBFRAME | CCD_HAS_STREAMING | CCD_CAN_ABORT);
200 #endif
201 
202     v4l_base->setDeviceName(getDeviceName());
203     return true;
204 }
205 
initCamBase()206 void V4L2_Driver::initCamBase()
207 {
208     v4l_base = new INDI::V4L2_Base();
209 }
210 
ISGetProperties(const char * dev)211 void V4L2_Driver::ISGetProperties(const char * dev)
212 {
213     if (dev != nullptr && strcmp(getDeviceName(), dev) != 0)
214         return;
215 
216     INDI::CCD::ISGetProperties(dev);
217 
218     defineProperty(&PortTP);
219     loadConfig(true, INDI::SP::DEVICE_PORT);
220 
221     if (isConnected())
222     {
223         defineProperty(&camNameTP);
224 
225         defineProperty(&ImageColorSP);
226         defineProperty(&InputsSP);
227         defineProperty(&CaptureFormatsSP);
228 
229         if (CaptureSizesSP.sp != nullptr)
230             defineProperty(&CaptureSizesSP);
231         else if (CaptureSizesNP.np != nullptr)
232             defineProperty(&CaptureSizesNP);
233         if (FrameRatesSP.sp != nullptr)
234             defineProperty(&FrameRatesSP);
235         else if (FrameRateNP.np != nullptr)
236             defineProperty(&FrameRateNP);
237 
238         defineProperty(&StackModeSP);
239 
240 #ifdef WITH_V4L2_EXPERIMENTS
241         defineProperty(&ImageDepthSP);
242         defineProperty(&ColorProcessingSP);
243         defineProperty(&CaptureColorSpaceTP);
244 #endif
245     }
246 }
247 
updateProperties()248 bool V4L2_Driver::updateProperties()
249 {
250     INDI::CCD::updateProperties();
251 
252     if (isConnected())
253     {
254         //ExposeTimeNP=getNumber("CCD_EXPOSURE");
255         //ExposeTimeN=ExposeTimeNP->np;
256 
257         CompressSP = getSwitch("CCD_COMPRESSION");
258         CompressS  = CompressSP->sp;
259 
260         FrameNP = getNumber("CCD_FRAME");
261         FrameN  = FrameNP->np;
262 
263         defineProperty(&camNameTP);
264         getBasicData();
265 
266         defineProperty(&ImageColorSP);
267         defineProperty(&InputsSP);
268         defineProperty(&CaptureFormatsSP);
269 
270         if (CaptureSizesSP.sp != nullptr)
271             defineProperty(&CaptureSizesSP);
272         else if (CaptureSizesNP.np != nullptr)
273             defineProperty(&CaptureSizesNP);
274         if (FrameRatesSP.sp != nullptr)
275             defineProperty(&FrameRatesSP);
276         else if (FrameRateNP.np != nullptr)
277             defineProperty(&FrameRateNP);
278 
279         defineProperty(&StackModeSP);
280 
281 #ifdef WITH_V4L2_EXPERIMENTS
282         defineProperty(&ImageDepthSP);
283         defineProperty(&ColorProcessingSP);
284         defineProperty(&CaptureColorSpaceTP);
285 #endif
286 
287         // Check if we have pixel size info
288         const PixelSizeInfo * info = pixelSizeInfo;
289         std::string deviceName = std::string(v4l_base->getDeviceName());
290         // to lower case.
291         std::transform(deviceName.begin(), deviceName.end(), deviceName.begin(), ::tolower);
292         while (info->deviceName)
293         {
294             std::string infoDeviceName = std::string(info->deviceName);
295             std::transform(infoDeviceName.begin(), infoDeviceName.end(), infoDeviceName.begin(), ::tolower);
296 
297             // Case insensitive comparision
298             if (infoDeviceName == deviceName)
299                 break;
300             ++info;
301         }
302 
303         const char * commonName = info->commonName;
304         float pixX             = info->pixelSizeX;
305         float pixY             = info->pixelSizeY;
306 
307         if (!commonName)
308             commonName = info->deviceName;
309         if (pixY < 0)
310             pixY = pixX;
311 
312         if (info->deviceName)
313         {
314             LOGF_INFO("Setting pixel size correctly for %s", commonName);
315             if (info->tested == false)
316             {
317                 LOGF_INFO("Please report that the camera worked: Name: %s/%s Detected and working, to https://bit.ly/2S1Vxjq",
318                           v4l_base->getDeviceName(), commonName);
319             }
320         }
321         else
322         {
323             LOGF_INFO("Setting pixel size to default of %5.2f", pixX);
324             LOGF_INFO("For future autodetection of pixel size, please report the following: Reported Name: %s, "
325                       "Common Name (Eg: NexImage 10), Pixel Size to the following thread: https://bit.ly/2S1Vxjq",
326                       v4l_base->getDeviceName());
327         }
328         SetCCDParams(V4LFrame->width, V4LFrame->height, V4LFrame->bpp, pixX, pixY);
329         PrimaryCCD.setImageExtension("fits");
330 
331         if (v4l_base->isLXmodCapable())
332             lx->updateProperties();
333         return true;
334     }
335     else
336     {
337         unsigned int i;
338 
339         if (v4l_base->isLXmodCapable())
340             lx->updateProperties();
341 
342         deleteProperty(camNameTP.name);
343 
344         deleteProperty(ImageColorSP.name);
345         deleteProperty(InputsSP.name);
346         deleteProperty(CaptureFormatsSP.name);
347 
348         if (CaptureSizesSP.sp != nullptr)
349             deleteProperty(CaptureSizesSP.name);
350         else if (CaptureSizesNP.np != nullptr)
351             deleteProperty(CaptureSizesNP.name);
352         if (FrameRatesSP.sp != nullptr)
353             deleteProperty(FrameRatesSP.name);
354         else if (FrameRateNP.np != nullptr)
355             deleteProperty(FrameRateNP.name);
356 
357         deleteProperty(ImageAdjustNP.name);
358         for (i = 0; i < v4loptions; i++)
359             deleteProperty(Options[i].name);
360         if (Options)
361             free(Options);
362         Options    = nullptr;
363         v4loptions = 0;
364 
365         deleteProperty(StackModeSP.name);
366 
367 #ifdef WITH_V4L2_EXPERIMENTS
368         deleteProperty(ImageDepthSP.name);
369         deleteProperty(ColorProcessingSP.name);
370         deleteProperty(CaptureColorSpaceTP.name);
371 #endif
372 
373         return true;
374     }
375 }
376 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)377 bool V4L2_Driver::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
378 {
379     char errmsg[ERRMSGSIZ];
380     unsigned int iopt;
381 
382     /* ignore if not ours */
383     if (dev != nullptr && strcmp(getDeviceName(), dev) != 0)
384         return true;
385 
386     /* Input */
387     if (strcmp(name, InputsSP.name) == 0)
388     {
389         //if ((StreamSP.s == IPS_BUSY) ||  (ExposeTimeNP->s == IPS_BUSY) || (RecordStreamSP.s == IPS_BUSY)) {
390         if (PrimaryCCD.isExposing() || Streamer->isBusy())
391         {
392             LOG_ERROR("Can not set input while capturing.");
393             InputsSP.s = IPS_ALERT;
394             IDSetSwitch(&InputsSP, nullptr);
395             return false;
396         }
397         else
398         {
399             unsigned int inputindex, oldindex;
400             oldindex = IUFindOnSwitchIndex(&InputsSP);
401             IUResetSwitch(&InputsSP);
402             IUUpdateSwitch(&InputsSP, states, names, n);
403             inputindex = IUFindOnSwitchIndex(&InputsSP);
404 
405             if (v4l_base->setinput(inputindex, errmsg) == -1)
406             {
407                 LOGF_INFO("ERROR (setinput): %s", errmsg);
408                 IUResetSwitch(&InputsSP);
409                 InputsSP.sp[oldindex].s = ISS_ON;
410                 InputsSP.s              = IPS_ALERT;
411                 IDSetSwitch(&InputsSP, nullptr);
412                 return false;
413             }
414 
415             deleteProperty(CaptureFormatsSP.name);
416             v4l_base->getcaptureformats(&CaptureFormatsSP);
417             defineProperty(&CaptureFormatsSP);
418             if (CaptureSizesSP.sp != nullptr)
419                 deleteProperty(CaptureSizesSP.name);
420             else if (CaptureSizesNP.np != nullptr)
421                 deleteProperty(CaptureSizesNP.name);
422 
423             v4l_base->getcapturesizes(&CaptureSizesSP, &CaptureSizesNP);
424 
425             if (CaptureSizesSP.sp != nullptr)
426                 defineProperty(&CaptureSizesSP);
427             else if (CaptureSizesNP.np != nullptr)
428                 defineProperty(&CaptureSizesNP);
429             InputsSP.s = IPS_OK;
430             IDSetSwitch(&InputsSP, nullptr);
431             LOGF_INFO("Capture input: %d. %s", inputindex, InputsSP.sp[inputindex].name);
432             return true;
433         }
434     }
435 
436     /* Capture Format */
437     if (strcmp(name, CaptureFormatsSP.name) == 0)
438     {
439         //if ((StreamSP.s == IPS_BUSY) ||  (ExposeTimeNP->s == IPS_BUSY) || (RecordStreamSP.s == IPS_BUSY)) {
440         if (PrimaryCCD.isExposing() || Streamer->isBusy())
441         {
442             LOG_ERROR("Can not set format while capturing.");
443             CaptureFormatsSP.s = IPS_ALERT;
444             IDSetSwitch(&CaptureFormatsSP, nullptr);
445             return false;
446         }
447         else
448         {
449             unsigned int index, oldindex;
450             oldindex = IUFindOnSwitchIndex(&CaptureFormatsSP);
451             IUResetSwitch(&CaptureFormatsSP);
452             IUUpdateSwitch(&CaptureFormatsSP, states, names, n);
453             index = IUFindOnSwitchIndex(&CaptureFormatsSP);
454 
455             if (v4l_base->setcaptureformat(*((unsigned int *)CaptureFormatsSP.sp[index].aux), errmsg) == -1)
456             {
457                 LOGF_INFO("ERROR (setformat): %s", errmsg);
458                 IUResetSwitch(&CaptureFormatsSP);
459                 CaptureFormatsSP.sp[oldindex].s = ISS_ON;
460                 CaptureFormatsSP.s              = IPS_ALERT;
461                 IDSetSwitch(&CaptureFormatsSP, nullptr);
462                 return false;
463             }
464 
465             V4LFrame->bpp = v4l_base->getBpp();
466             PrimaryCCD.setBPP(V4LFrame->bpp);
467 
468             if (CaptureSizesSP.sp != nullptr)
469                 deleteProperty(CaptureSizesSP.name);
470             else if (CaptureSizesNP.np != nullptr)
471                 deleteProperty(CaptureSizesNP.name);
472             v4l_base->getcapturesizes(&CaptureSizesSP, &CaptureSizesNP);
473 
474             if (CaptureSizesSP.sp != nullptr)
475                 defineProperty(&CaptureSizesSP);
476             else if (CaptureSizesNP.np != nullptr)
477                 defineProperty(&CaptureSizesNP);
478             CaptureFormatsSP.s = IPS_OK;
479 
480 #ifdef WITH_V4L2_EXPERIMENTS
481             IUSaveText(&CaptureColorSpaceT[0], getColorSpaceName(&v4l_base->fmt));
482             IUSaveText(&CaptureColorSpaceT[1], getYCbCrEncodingName(&v4l_base->fmt));
483             IUSaveText(&CaptureColorSpaceT[2], getQuantizationName(&v4l_base->fmt));
484             IDSetText(&CaptureColorSpaceTP, nullptr);
485 #endif
486             //direct_record=recorder->setpixelformat(v4l_base->fmt.fmt.pix.pixelformat);
487             INDI_PIXEL_FORMAT pixelFormat;
488             uint8_t pixelDepth = 8;
489             if (getPixelFormat(v4l_base->fmt.fmt.pix.pixelformat, pixelFormat, pixelDepth))
490                 Streamer->setPixelFormat(pixelFormat, pixelDepth);
491 
492             saveConfig(true, CaptureFormatsSP.name);
493             IDSetSwitch(&CaptureFormatsSP, "Capture format: %d. %s", index, CaptureFormatsSP.sp[index].name);
494             return true;
495         }
496     }
497 
498     /* Capture Size (Discrete) */
499     if (strcmp(name, CaptureSizesSP.name) == 0)
500     {
501         //if ((StreamSP.s == IPS_BUSY) ||  (ExposeTimeNP->s == IPS_BUSY) || (RecordStreamSP.s == IPS_BUSY)) {
502         if (PrimaryCCD.isExposing() || Streamer->isBusy())
503         {
504             LOG_ERROR("Can not set capture size while capturing.");
505             CaptureSizesSP.s = IPS_ALERT;
506             IDSetSwitch(&CaptureSizesSP, nullptr);
507             return false;
508         }
509         else
510         {
511             unsigned int index, w, h;
512             IUUpdateSwitch(&CaptureSizesSP, states, names, n);
513             index = IUFindOnSwitchIndex(&CaptureSizesSP);
514             sscanf(CaptureSizesSP.sp[index].name, "%dx%d", &w, &h);
515             if (v4l_base->setcapturesize(w, h, errmsg) == -1)
516             {
517                 LOGF_INFO("ERROR (setsize): %s", errmsg);
518                 CaptureSizesSP.s = IPS_ALERT;
519                 IDSetSwitch(&CaptureSizesSP, nullptr);
520                 return false;
521             }
522 
523             if (FrameRatesSP.sp != nullptr)
524                 deleteProperty(FrameRatesSP.name);
525             else if (FrameRateNP.np != nullptr)
526                 deleteProperty(FrameRateNP.name);
527             v4l_base->getframerates(&FrameRatesSP, &FrameRateNP);
528             if (FrameRatesSP.sp != nullptr)
529                 defineProperty(&FrameRatesSP);
530             else if (FrameRateNP.np != nullptr)
531                 defineProperty(&FrameRateNP);
532 
533             PrimaryCCD.setFrame(0, 0, w, h);
534             V4LFrame->width  = w;
535             V4LFrame->height = h;
536             PrimaryCCD.setResolution(w, h);
537             updateFrameSize();
538             Streamer->setSize(w, h);
539 
540             CaptureSizesSP.s = IPS_OK;
541             IDSetSwitch(&CaptureSizesSP, "Capture size (discrete): %d. %s", index, CaptureSizesSP.sp[index].name);
542 
543             saveConfig(true, CaptureSizesSP.name);
544             return true;
545         }
546     }
547 
548     /* Frame Rate (Discrete) */
549     if (strcmp(name, FrameRatesSP.name) == 0)
550     {
551         if (PrimaryCCD.isExposing() || Streamer->isBusy())
552         {
553             LOG_ERROR("Can not change frame rate while capturing.");
554             FrameRatesSP.s = IPS_ALERT;
555             IDSetSwitch(&FrameRatesSP, nullptr);
556             return false;
557         }
558         unsigned int index;
559         struct v4l2_fract frate;
560         IUUpdateSwitch(&FrameRatesSP, states, names, n);
561         index = IUFindOnSwitchIndex(&FrameRatesSP);
562         sscanf(FrameRatesSP.sp[index].name, "%d/%d", &frate.numerator, &frate.denominator);
563         if ((v4l_base->*(v4l_base->setframerate))(frate, errmsg) == -1)
564         {
565             LOGF_INFO("ERROR (setframerate): %s", errmsg);
566             FrameRatesSP.s = IPS_ALERT;
567             IDSetSwitch(&FrameRatesSP, nullptr);
568             return false;
569         }
570 
571         FrameRatesSP.s = IPS_OK;
572         IDSetSwitch(&FrameRatesSP, "Frame Period (discrete): %d. %s", index, FrameRatesSP.sp[index].name);
573         return true;
574     }
575 
576     /* Image Type */
577     if (strcmp(name, ImageColorSP.name) == 0)
578     {
579         if (Streamer->isRecording())
580         {
581             LOG_WARN("Can not set Image type (GRAY/COLOR) while recording.");
582             return false;
583         }
584 
585         IUResetSwitch(&ImageColorSP);
586         IUUpdateSwitch(&ImageColorSP, states, names, n);
587         ImageColorSP.s = IPS_OK;
588         if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON)
589         {
590             //PrimaryCCD.setBPP(8);
591             PrimaryCCD.setNAxis(2);
592         }
593         else
594         {
595             //PrimaryCCD.setBPP(32);
596             //PrimaryCCD.setBPP(8);
597             PrimaryCCD.setNAxis(3);
598         }
599 
600         updateFrameSize();
601 #if 0
602         INDI_PIXEL_FORMAT pixelFormat;
603         uint8_t pixelDepth = 8;
604         if (getPixelFormat(v4l_base->fmt.fmt.pix.pixelformat, pixelFormat, pixelDepth))
605             Streamer->setPixelFormat(pixelFormat, pixelDepth);
606 #endif
607         Streamer->setPixelFormat((ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON) ? INDI_MONO : INDI_RGB, 8);
608         IDSetSwitch(&ImageColorSP, nullptr);
609 
610         saveConfig(true, ImageColorSP.name);
611         return true;
612     }
613 
614     /* Image Depth */
615     if (strcmp(name, ImageDepthSP.name) == 0)
616     {
617         if (Streamer->isRecording())
618         {
619             LOG_WARN("Can not set Image depth (8/16bits) while recording.");
620             return false;
621         }
622 
623         IUResetSwitch(&ImageDepthSP);
624         IUUpdateSwitch(&ImageDepthSP, states, names, n);
625         ImageDepthSP.s = IPS_OK;
626         if (ImageDepthS[0].s == ISS_ON)
627         {
628             PrimaryCCD.setBPP(8);
629         }
630         else
631         {
632             PrimaryCCD.setBPP(16);
633         }
634         IDSetSwitch(&ImageDepthSP, nullptr);
635         return true;
636     }
637 
638     /* Stacking Mode */
639     if (strcmp(name, StackModeSP.name) == 0)
640     {
641         IUResetSwitch(&StackModeSP);
642         IUUpdateSwitch(&StackModeSP, states, names, n);
643         StackModeSP.s = IPS_OK;
644         stackMode     = IUFindOnSwitchIndex(&StackModeSP);
645         if (stackMode == STACK_RESET_DARK)
646         {
647             if (V4LFrame->darkFrame != nullptr)
648             {
649                 free(V4LFrame->darkFrame);
650                 V4LFrame->darkFrame = nullptr;
651             }
652         }
653 
654         IDSetSwitch(&StackModeSP, "Setting Stacking Mode: %s", StackModeS[stackMode].name);
655         return true;
656     }
657 
658     /* V4L2 Options/Menus */
659     for (iopt = 0; iopt < v4loptions; iopt++)
660         if (strcmp(Options[iopt].name, name) == 0)
661             break;
662     if (iopt < v4loptions)
663     {
664         unsigned int ctrl_id, optindex, ctrlindex;
665 
666         LOGF_DEBUG("Toggle switch %s=%s", Options[iopt].name, Options[iopt].label);
667 
668         Options[iopt].s = IPS_IDLE;
669         IUResetSwitch(&Options[iopt]);
670         if (IUUpdateSwitch(&Options[iopt], states, names, n) < 0)
671             return false;
672 
673         optindex = IUFindOnSwitchIndex(&Options[iopt]);
674         if (Options[iopt].sp[optindex].aux != nullptr)
675             ctrlindex = *(unsigned int *)(Options[iopt].sp[optindex].aux);
676         else
677             ctrlindex = optindex;
678         ctrl_id = (*((unsigned int *)Options[iopt].aux));
679         LOGF_DEBUG("  On switch is (%d) %s=\"%s\", ctrl_id = 0x%X ctrl_index=%d", optindex,
680                    Options[iopt].sp[optindex].name, Options[iopt].sp[optindex].label, ctrl_id, ctrlindex);
681         if (v4l_base->setOPTControl(ctrl_id, ctrlindex, errmsg) < 0)
682         {
683             if (Options[iopt].nsp == 1) // button
684             {
685                 Options[iopt].sp[optindex].s = ISS_OFF;
686             }
687             Options[iopt].s = IPS_ALERT;
688             IDSetSwitch(&Options[iopt], nullptr);
689             LOGF_ERROR("Unable to adjust setting. %s", errmsg);
690             return false;
691         }
692         if (Options[iopt].nsp == 1) // button
693         {
694             Options[iopt].sp[optindex].s = ISS_OFF;
695         }
696         Options[iopt].s = IPS_OK;
697         IDSetSwitch(&Options[iopt], nullptr);
698         return true;
699     }
700 
701     /* ColorProcessing */
702     if (strcmp(name, ColorProcessingSP.name) == 0)
703     {
704         if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON)
705         {
706             IUUpdateSwitch(&ColorProcessingSP, states, names, n);
707             v4l_base->setColorProcessing(ColorProcessingS[0].s == ISS_ON, ColorProcessingS[1].s == ISS_ON,
708                                          ColorProcessingS[2].s == ISS_ON);
709             ColorProcessingSP.s = IPS_OK;
710             IDSetSwitch(&ColorProcessingSP, nullptr);
711             V4LFrame->bpp = v4l_base->getBpp();
712             PrimaryCCD.setBPP(V4LFrame->bpp);
713             PrimaryCCD.setBPP(V4LFrame->bpp);
714             updateFrameSize();
715             return true;
716         }
717         else
718         {
719             LOG_WARN("No color processing in color mode ");
720             return false;
721         }
722     }
723     lx->ISNewSwitch(dev, name, states, names, n);
724     return INDI::CCD::ISNewSwitch(dev, name, states, names, n);
725 }
726 
ISNewText(const char * dev,const char * name,char * texts[],char * names[],int n)727 bool V4L2_Driver::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
728 {
729     IText * tp;
730 
731     /* ignore if not ours */
732     if (dev != nullptr && strcmp(getDeviceName(), dev) != 0)
733         return true;
734 
735     if (strcmp(name, PortTP.name) == 0)
736     {
737         PortTP.s = IPS_OK;
738         tp = IUFindText(&PortTP, names[0]);
739         if (!tp)
740             return false;
741         IUSaveText(tp, texts[0]);
742         IDSetText(&PortTP, nullptr);
743 
744         saveConfig(true, PortTP.name);
745         return true;
746     }
747 
748     lx->ISNewText(dev, name, texts, names, n);
749     return INDI::CCD::ISNewText(dev, name, texts, names, n);
750 }
751 
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)752 bool V4L2_Driver::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
753 {
754     char errmsg[ERRMSGSIZ];
755 
756     /* ignore if not ours */
757     if (dev != nullptr && strcmp(getDeviceName(), dev) != 0)
758         return true;
759 
760     /* Capture Size (Step/Continuous) */
761     if (strcmp(name, CaptureSizesNP.name) == 0)
762     {
763         if (PrimaryCCD.isExposing() || Streamer->isBusy())
764         {
765             LOG_ERROR("Can not set capture size while capturing.");
766             CaptureSizesNP.s = IPS_BUSY;
767             IDSetNumber(&CaptureSizesNP, nullptr);
768             return false;
769         }
770         else
771         {
772             unsigned int sizes[2], w = 0, h = 0;
773             double rsizes[2];
774 
775             if (strcmp(names[0], "Width") == 0)
776             {
777                 sizes[0] = values[0];
778                 sizes[1] = values[1];
779             }
780             else
781             {
782                 sizes[0] = values[1];
783                 sizes[1] = values[0];
784             }
785             if (v4l_base->setcapturesize(sizes[0], sizes[1], errmsg) == -1)
786             {
787                 LOGF_INFO("ERROR (setsize): %s", errmsg);
788                 CaptureSizesNP.s = IPS_ALERT;
789                 IDSetNumber(&CaptureSizesNP, nullptr);
790                 return false;
791             }
792             if (strcmp(names[0], "Width") == 0)
793             {
794                 w         = v4l_base->getWidth();
795                 rsizes[0] = (double)w;
796                 h         = v4l_base->getHeight();
797                 rsizes[1] = (double)h;
798             }
799             else
800             {
801                 w         = v4l_base->getWidth();
802                 rsizes[1] = (double)w;
803                 h         = v4l_base->getHeight();
804                 rsizes[0] = (double)h;
805             }
806 
807             PrimaryCCD.setFrame(0, 0, w, h);
808             IUUpdateNumber(&CaptureSizesNP, rsizes, names, n);
809             V4LFrame->width  = w;
810             V4LFrame->height = h;
811             PrimaryCCD.setResolution(w, h);
812             CaptureSizesNP.s = IPS_OK;
813             updateFrameSize();
814             Streamer->setSize(w, h);
815 
816             IDSetNumber(&CaptureSizesNP, "Capture size (step/cont): %dx%d", w, h);
817             return true;
818         }
819     }
820 
821     if (strcmp(ImageAdjustNP.name, name) == 0)
822     {
823         ImageAdjustNP.s = IPS_IDLE;
824 
825         if (IUUpdateNumber(&ImageAdjustNP, values, names, n) < 0)
826             return false;
827 
828         for (int i = 0; i < ImageAdjustNP.nnp; i++)
829         {
830             unsigned int const ctrl_id = *((unsigned int *)ImageAdjustNP.np[i].aux0);
831             double const value = ImageAdjustNP.np[i].value;
832 
833             LOGF_DEBUG("  Setting %s (%s) to %f, ctrl_id = 0x%X", ImageAdjustNP.np[i].name,
834                        ImageAdjustNP.np[i].label, value, ctrl_id);
835 
836             if (v4l_base->setINTControl(ctrl_id, ImageAdjustNP.np[i].value, errmsg) < 0)
837             {
838                 /* Some controls may become read-only depending on selected options */
839                 LOGF_WARN("Unable to adjust %s (ctrl_id =  0x%X)", ImageAdjustNP.np[i].label,
840                           ctrl_id);
841             }
842             /* Some controls may have been ajusted by the driver */
843             /* a read is mandatory as VIDIOC_S_CTRL is write only and does not return the actual new value */
844             v4l_base->getControl(ctrl_id, &(ImageAdjustNP.np[i].value), errmsg);
845 
846             /* Warn the client if the control returned another value than what was set */
847             if(value != ImageAdjustNP.np[i].value)
848             {
849                 LOGF_WARN("Control %s set to %f returned %f (ctrl_id =  0x%X)",
850                           ImageAdjustNP.np[i].label, value, ImageAdjustNP.np[i].value, ctrl_id);
851             }
852         }
853         ImageAdjustNP.s = IPS_OK;
854         IDSetNumber(&ImageAdjustNP, nullptr);
855         return true;
856     }
857 
858     return INDI::CCD::ISNewNumber(dev, name, values, names, n);
859 }
860 
StartExposure(float duration)861 bool V4L2_Driver::StartExposure(float duration)
862 {
863     /* Clicking the "Expose" set button while an exposure is running arrives here.
864      * Now that V4L2 CCD has the option to abort, this will properly abort the exposure.
865      * If CAN_ABORT is not set, we have to tell the caller we're busy until the end of this exposure.
866      * If we don't, PrimaryCCD will stop exposing nonetheless and we won't be able to restart an exposure.
867      */
868     {
869         if (Streamer->isBusy())
870         {
871             LOG_ERROR("Cannot start new exposure while streamer is busy, stop streaming first");
872             return !(GetCCDCapability() & CCD_CAN_ABORT);
873         }
874 
875         if (is_capturing)
876         {
877             LOGF_ERROR(
878                 "Cannot start new exposure until the current one completes (%.3f seconds left).",
879                 getRemainingExposure());
880             return !(GetCCDCapability() & CCD_CAN_ABORT);
881 
882             return true;
883         }
884     }
885 
886     if (setShutter(duration))
887     {
888         V4LFrame->expose = duration;
889         PrimaryCCD.setExposureDuration(duration);
890 
891         if (!lx->isEnabled() || lx->getLxmode() == LXSERIAL)
892             start_capturing(false);
893 
894         /* Update exposure duration in client */
895         /* FIXME: exposure update timer has period hardcoded 1 second */
896         if (is_capturing && 1.0f < duration)
897         {
898             //if (-1 != stdtimer)
899             //    IERmTimer(stdtimer);
900             //stdtimer = IEAddTimer(1000, (IE_TCF *)stdtimerCallback, this);
901             stdtimer = -1;
902         }
903         else
904             stdtimer = -1;
905     }
906 
907     return is_capturing;
908 }
909 
setShutter(double duration)910 bool V4L2_Driver::setShutter(double duration)
911 {
912     if (lx->isEnabled())
913     {
914         LOGF_INFO("Using long exposure mode for %.3f sec frame.", duration);
915         if (startlongexposure(duration))
916         {
917             LOGF_INFO("Started %.3f-second long exposure.", duration);
918             return true;
919         }
920         else
921         {
922             DEBUGF(INDI::Logger::DBG_WARNING,
923                    "Unable to start %.3f-second long exposure, falling back to auto exposure", duration);
924             return false;
925         }
926     }
927     else if (setManualExposure(duration))
928     {
929         exposure_duration.tv_sec  = (long) duration;
930         exposure_duration.tv_usec = (long) ((duration - (double) exposure_duration.tv_sec) * 1000000.0f);
931 
932         elapsed_exposure.tv_sec = 0;
933         elapsed_exposure.tv_usec = 0;
934 
935         gettimeofday(&capture_start, nullptr);
936 
937         frameCount    = 0;
938         subframeCount = 0;
939 
940         LOGF_INFO("Started %.3f-second manual exposure.", duration);
941         return true;
942     }
943     else
944     {
945         LOGF_WARN("Failed %.3f-second manual exposure, no adequate control is registered.",
946                   duration);
947         return false;
948     }
949 }
950 
setManualExposure(double duration)951 bool V4L2_Driver::setManualExposure(double duration)
952 {
953     /* N.B. Check how this differs from one camera to another. This is just a proof of concept for now */
954     /* With DMx 21AU04.AS, exposing twice with the same duration causes an incomplete frame to pop in the buffer list
955      * This can be worked around by verifying the buffer size, but it won't work for anything else than Y8/Y16, so set
956      * exposure unconditionally */
957     /*if (duration * 10000 != AbsExposureN->value)*/
958 
959     // INT control for manual exposure duration is an integer in 1/10000 seconds
960     long ticks = lround(duration * 10000.0f);
961 
962     /* First check the presence of an absolute exposure control */
963     if (nullptr == AbsExposureN)
964     {
965         /* We don't have an absolute exposure control but we can stack gray frames until the exposure elapses */
966         if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON && stackMode != STACK_NONE && stackMode != STACK_RESET_DARK)
967         {
968             LOGF_WARN("Absolute exposure duration control is undefined, stacking up to %.3f seconds using %.16s.",
969                       duration, StackModeS[stackMode].name);
970             return true;
971         }
972         /* We don't have an absolute exposure control and stacking is not configured, bail out */
973         else
974         {
975             LOGF_ERROR("Failed exposing, the absolute exposure duration control is undefined, and stacking is not ready.", "");
976             LOGF_ERROR("Configure grayscale and stacking in order to stack streamed frames up to %.3f seconds.", duration);
977             return false;
978         }
979     }
980     /* Then if we have an exposure control, check the requested exposure duration */
981     else if (AbsExposureN->max < ticks)
982     {
983         if( ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON && stackMode == STACK_NONE )
984         {
985             LOG_WARN("Requested manual exposure is out of device bounds auto set stackMode to ADDITIVE" );
986             stackMode = STACK_ADDITIVE;
987             StackModeSP.sp[ STACK_NONE ].s = ISS_OFF;
988             StackModeSP.sp[ STACK_ADDITIVE ].s = ISS_ON;
989             if(ManualExposureSP) IDSetSwitch(ManualExposureSP, nullptr);
990         }
991 
992         /* We can't expose as long as requested but we can stack gray frames until the exposure elapses */
993         if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON && stackMode != STACK_NONE && stackMode != STACK_RESET_DARK)
994         {
995             if( AbsExposureN->value != AbsExposureN->max )
996             {
997                 LOGF_WARN("Requested manual exposure is out of device bounds [%.3f,%.3f], stacking up to %.3f seconds using %.16s.",
998                           (double) AbsExposureN->min / 10000.0f, (double) AbsExposureN->max / 10000.0f,
999                           duration, StackModeS[stackMode].name);
1000             }
1001             ticks = AbsExposureN->max;
1002         }
1003         /* We can't expose as long as requested and stacking is not configured, bail out */
1004         else
1005         {
1006             LOGF_ERROR("Failed %.3f-second manual exposure, out of device bounds [%.3f,%.3f], and stacking is not ready.",
1007                        duration, (double) AbsExposureN->min / 10000.0f, (double) AbsExposureN->max / 10000.0f);
1008             LOGF_ERROR("Configure grayscale and stacking in order to stack streamed frames up to %.3f seconds.", duration);
1009             return false;
1010         }
1011     }
1012     /* Lower-than-minimal exposure duration is left managed below */
1013 
1014 
1015     frame_duration.tv_sec  = ticks / 10000;
1016     frame_duration.tv_usec = (ticks % 10000) * 100;
1017 
1018     if( v4l_capture_started )
1019     {
1020         if( AbsExposureN->value != ticks )
1021         {
1022             stop_capturing();
1023         }
1024         else
1025         {
1026             return true;
1027         }
1028     }
1029 
1030 
1031     /* At this point we do have an absolute exposure control and a valid exposure duration, so start exposing. */
1032 
1033     /* Manual mode should be set before changing Exposure (Auto), if possible.
1034      * In some cases there might be no control available, so don't fail and try to continue.
1035      */
1036     if (ManualExposureSP)
1037     {
1038         if (ManualExposureSP->sp[0].s == ISS_OFF)
1039         {
1040             ManualExposureSP->sp[0].s = ISS_ON;
1041             ManualExposureSP->sp[1].s = ISS_OFF;
1042             ManualExposureSP->s       = IPS_IDLE;
1043 
1044             unsigned int const ctrlindex = ManualExposureSP->sp[0].aux ? *(unsigned int *)(ManualExposureSP->sp[0].aux) : 0;
1045             unsigned int const ctrl_id = (*((unsigned int *)ManualExposureSP->aux));
1046 
1047             char errmsg[MAXRBUF];
1048             if (v4l_base->setOPTControl(ctrl_id, ctrlindex, errmsg) < 0)
1049             {
1050                 ManualExposureSP->sp[0].s = ISS_OFF;
1051                 ManualExposureSP->sp[1].s = ISS_ON;
1052                 ManualExposureSP->s       = IPS_ALERT;
1053                 IDSetSwitch(ManualExposureSP, nullptr);
1054 
1055                 LOGF_ERROR("Unable to adjust manual/auto exposure control. %s", errmsg);
1056                 return false;
1057             }
1058 
1059             ManualExposureSP->s = IPS_OK;
1060             IDSetSwitch(ManualExposureSP, nullptr);
1061         }
1062     }
1063     else
1064     {
1065         LOGF_WARN("Failed switching to manual exposure, control is unavailable", "");
1066         /* Don't fail, let the driver try to set the absolute duration, we'll see what happens */
1067         /* return false; */
1068     }
1069 
1070     /* Configure absolute exposure */
1071     if (AbsExposureN->min <= ticks && ticks <= AbsExposureN->max)
1072     {
1073         double const restoredValue = AbsExposureN->value;
1074         AbsExposureN->value = ticks;
1075 
1076         LOGF_DEBUG("%.3f-second exposure translates to %ld 1/10,000th-second device ticks.",
1077                    duration, ticks);
1078 
1079         unsigned int const ctrl_id = *((unsigned int *)AbsExposureN->aux0);
1080 
1081         char errmsg[MAXRBUF];
1082         if (v4l_base->setINTControl(ctrl_id, AbsExposureN->value, errmsg) < 0)
1083         {
1084             ImageAdjustNP.s     = IPS_ALERT;
1085             AbsExposureN->value = restoredValue;
1086             IDSetNumber(&ImageAdjustNP, "Failed requesting %.3f-second exposure to the driver (%s).", duration, errmsg);
1087             return false;
1088         }
1089 
1090         ImageAdjustNP.s = IPS_OK;
1091         IDSetNumber(&ImageAdjustNP, nullptr);
1092     }
1093     else
1094     {
1095         LOGF_ERROR("Failed %.3f-second manual exposure, out of device bounds [%.3f,%.3f].",
1096                    duration, (double) AbsExposureN->min / 10000.0f, (double) AbsExposureN->max / 10000.0f);
1097         return false;
1098     }
1099 
1100     return true;
1101 }
1102 
1103 /** \internal Timer callback.
1104  *
1105  * This provides a very rough estimation of the remaining exposure to the client.
1106  */
stdtimerCallback(void * userpointer)1107 void V4L2_Driver::stdtimerCallback(void * userpointer)
1108 {
1109     V4L2_Driver * p = (V4L2_Driver *)userpointer;
1110     float remaining = p->getRemainingExposure();
1111     //DEBUGF(INDI::Logger::DBG_SESSION,"Exposure running, %f seconds left...", remaining);
1112     if (1.0f < remaining)
1113         p->stdtimer = IEAddTimer(1000, (IE_TCF *)stdtimerCallback, userpointer);
1114     else
1115         p->stdtimer = -1;
1116     p->PrimaryCCD.setExposureLeft(remaining);
1117 }
1118 
start_capturing(bool do_stream)1119 bool V4L2_Driver::start_capturing(bool do_stream)
1120 {
1121     // FIXME Must migrate completely to Stream
1122     // The class shouldn't be making calls to encoder/recorder directly
1123     // Stream? Yes or No
1124     // Direct Record?
1125     INDI_UNUSED(do_stream);
1126     if (Streamer->isBusy())
1127     {
1128         LOG_WARN("Cannot start exposure while streaming is in progress");
1129         return false;
1130     }
1131 
1132     if (is_capturing)
1133     {
1134         LOGF_WARN("Cannot start exposure while another is in progress (%.3f seconds left)",
1135                   getRemainingExposure());
1136         return false;
1137     }
1138 
1139     if( !v4l_capture_started )
1140     {
1141         char errmsg[ERRMSGSIZ];
1142         if (v4l_base->start_capturing(errmsg))
1143         {
1144             LOGF_WARN("V4L2 base failed starting capture (%s)", errmsg);
1145             return false;
1146         }
1147         else
1148         {
1149             gettimeofday(&frame_received, nullptr);
1150             v4l_capture_started = true;
1151         }
1152     }
1153 
1154     //if (do_stream)
1155     //v4l_base->doRecord(Streamer->isDirectRecording());
1156 
1157     is_capturing = true;
1158     return true;
1159 }
1160 
stop_capturing()1161 bool V4L2_Driver::stop_capturing()
1162 {
1163     if (!is_capturing && !v4l_capture_started)
1164     {
1165         LOG_WARN("No exposure or streaming in progress");
1166         return true;
1167     }
1168 
1169     if (!Streamer->isBusy() && 0.0f < getRemainingExposure())
1170     {
1171         LOGF_WARN("Stopping running exposure %.3f seconds before completion",
1172                   getRemainingExposure());
1173     }
1174 
1175     // FIXME what to do with doRecord?
1176     //if(Streamer->isDirectRecording())
1177     //v4l_base->doRecord(false);
1178     char errmsg[ERRMSGSIZ];
1179     if (v4l_base->stop_capturing(errmsg))
1180     {
1181         LOGF_WARN("V4L2 base failed stopping capture (%s)", errmsg);
1182     }
1183 
1184     is_capturing = false;
1185     v4l_capture_started = false;
1186     return true;
1187 }
1188 
startlongexposure(double timeinsec)1189 bool V4L2_Driver::startlongexposure(double timeinsec)
1190 {
1191     lxtimer = IEAddTimer((int)(timeinsec * 1000.0), (IE_TCF *)lxtimerCallback, this);
1192     v4l_base->setlxstate(LX_ACCUMULATING);
1193     return (lx->startLx());
1194 }
1195 
lxtimerCallback(void * userpointer)1196 void V4L2_Driver::lxtimerCallback(void * userpointer)
1197 {
1198     V4L2_Driver * p = (V4L2_Driver *)userpointer;
1199 
1200     p->lx->stopLx();
1201     if (p->lx->getLxmode() == LXSERIAL)
1202     {
1203         p->v4l_base->setlxstate(LX_TRIGGERED);
1204     }
1205     else
1206     {
1207         p->v4l_base->setlxstate(LX_ACTIVE);
1208     }
1209     IERmTimer(p->lxtimer);
1210     if (!p->v4l_base->isstreamactive())
1211         p->is_capturing = p->start_capturing(false); // jump to new/updateFrame
1212     //p->v4l_base->start_capturing(errmsg); // jump to new/updateFrame
1213 }
1214 
UpdateCCDBin(int hor,int ver)1215 bool V4L2_Driver::UpdateCCDBin(int hor, int ver)
1216 {
1217     if (ImageColorS[IMAGE_COLOR].s == ISS_ON)
1218     {
1219         if (hor == 1 && ver == 1)
1220         {
1221             PrimaryCCD.setBin(hor, ver);
1222             Streamer->setSize(PrimaryCCD.getSubW(), PrimaryCCD.getSubH());
1223             return true;
1224         }
1225 
1226         LOG_WARN("Binning color frames is currently not supported.");
1227         return false;
1228     }
1229 
1230     if (hor != ver)
1231     {
1232         LOGF_WARN("Cannot accept asymmetrical binning %dx%d.", hor, ver);
1233         return false;
1234     }
1235 
1236     if (hor != 1 && hor != 2 && hor != 4)
1237     {
1238         LOG_WARN("Can only accept 1x1, 2x2, and 4x4 binning.");
1239         return false;
1240     }
1241 
1242     if (Streamer->isBusy())
1243     {
1244         LOG_WARN("Cannot change binning while streaming/recording.");
1245         return false;
1246     }
1247 
1248     PrimaryCCD.setBin(hor, ver);
1249     Streamer->setSize(PrimaryCCD.getSubW() / hor, PrimaryCCD.getSubH() / ver);
1250 
1251     return true;
1252 }
1253 
UpdateCCDFrame(int x,int y,int w,int h)1254 bool V4L2_Driver::UpdateCCDFrame(int x, int y, int w, int h)
1255 {
1256     char errmsg[ERRMSGSIZ];
1257 
1258     //LOGF_INFO("calling updateCCDFrame: %d %d %d %d", x, y, w, h);
1259     //IDLog("calling updateCCDFrame: %d %d %d %d\n", x, y, w, h);
1260     if (v4l_base->setcroprect(x, y, w, h, errmsg) != -1)
1261     {
1262         struct v4l2_rect crect;
1263         crect = v4l_base->getcroprect();
1264 
1265         V4LFrame->width  = crect.width;
1266         V4LFrame->height = crect.height;
1267         PrimaryCCD.setFrame(x, y, w, h);
1268         updateFrameSize();
1269         Streamer->setSize(w, h);
1270         return true;
1271     }
1272     else
1273     {
1274         LOGF_INFO("ERROR (setcroprect): %s", errmsg);
1275     }
1276 
1277     return false;
1278 }
1279 
newFrame(void * p)1280 void V4L2_Driver::newFrame(void * p)
1281 {
1282     ((V4L2_Driver *)(p))->newFrame();
1283 }
1284 
1285 /** @internal Stack normalized luminance pixels coming from the camera in an accumulator frame.
1286  */
stackFrame()1287 void V4L2_Driver::stackFrame()
1288 {
1289     /* FIXME: use unsigned floats, or double */
1290     size_t const size      = v4l_base->getWidth() * v4l_base->getHeight();
1291     float const * src       = v4l_base->getLinearY();
1292     float const * const end = v4l_base->getLinearY() + size;
1293     float * dest            = V4LFrame->stackedFrame;
1294 
1295     if (!V4LFrame->stackedFrame)
1296     {
1297         /* FIXME: allocate and reset the accumulator frame prior to this function, because memory owner is unclear, and we need more speed while accumulating */
1298         V4LFrame->stackedFrame = (float *)malloc(sizeof(float) * size);
1299         memcpy(V4LFrame->stackedFrame, src, sizeof(float) * size);
1300         subframeCount = 1;
1301     }
1302     else
1303     {
1304         /* Clamp to max float value */
1305         float const frameMax = std::numeric_limits<float>::max();
1306         while (src < end) if (frameMax - *dest < *src)
1307             {
1308                 *dest++ = frameMax;
1309                 src++;
1310             }
1311             else *dest++ += *src++;
1312         subframeCount += 1;
1313     }
1314 }
1315 
getElapsedExposure() const1316 struct timeval V4L2_Driver::getElapsedExposure() const
1317 {
1318     struct timeval now = { .tv_sec = 0, .tv_usec = 0 }, duration = { .tv_sec = 0, .tv_usec = 0 };
1319     gettimeofday( &now, nullptr );
1320     timersub(&now, &capture_start, &duration);
1321     return duration;
1322 }
1323 
getRemainingExposure() const1324 float V4L2_Driver::getRemainingExposure() const
1325 {
1326     struct timeval remaining = { .tv_sec = 0, .tv_usec = 0 };
1327     timersub(&exposure_duration, &elapsed_exposure, &remaining);
1328     return (float) remaining.tv_sec + (float) remaining.tv_usec / 1000000.0f;
1329 }
1330 
newFrame()1331 void V4L2_Driver::newFrame()
1332 {
1333     struct timeval current_frame_duration = frame_received;
1334     gettimeofday(&frame_received, nullptr);
1335     timersub(&frame_received, &current_frame_duration, &current_frame_duration);
1336 
1337 
1338     if (Streamer->isBusy())
1339     {
1340         non_capture_frames = 0;
1341 
1342         int width             = v4l_base->getWidth();
1343         int height            = v4l_base->getHeight();
1344         int bpp               = v4l_base->getBpp();
1345         int dbpp              = 8;
1346         int totalBytes        = 0;
1347         unsigned char * buffer = nullptr;
1348 
1349         std::unique_lock<std::mutex> guard(ccdBufferLock);
1350         if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON)
1351         {
1352             V4LFrame->Y = v4l_base->getY();
1353             totalBytes  = width * height * (dbpp / 8);
1354             buffer      = V4LFrame->Y;
1355         }
1356         else
1357         {
1358             V4LFrame->RGB24Buffer = v4l_base->getRGBBuffer();
1359             totalBytes            = width * height * (dbpp / 8) * 3;
1360             buffer                = V4LFrame->RGB24Buffer;
1361         }
1362 
1363         // downscale Y10 Y12 Y16
1364         if (bpp > dbpp)
1365         {
1366             unsigned short * src = (unsigned short *)buffer;
1367             unsigned char * dest = buffer;
1368             unsigned char shift = 0;
1369 
1370             if (bpp < 16)
1371             {
1372                 switch (bpp)
1373                 {
1374                     case 10:
1375                         shift = 2;
1376                         break;
1377                     case 12:
1378                         shift = 4;
1379                         break;
1380                 }
1381                 for (int i = 0; i < totalBytes; i++)
1382                 {
1383                     *dest++ = *(src++) >> shift;
1384                 }
1385             }
1386             else
1387             {
1388                 unsigned char * src = (unsigned char *)buffer + 1; // Y16 is little endian
1389 
1390                 for (int i = 0; i < totalBytes; i++)
1391                 {
1392                     *dest++ = *src;
1393                     src += 2;
1394                 }
1395             }
1396         }
1397 
1398         if (PrimaryCCD.getBinX() > 1)
1399         {
1400             memcpy(PrimaryCCD.getFrameBuffer(), buffer, totalBytes);
1401             PrimaryCCD.binFrame();
1402             guard.unlock();
1403             Streamer->newFrame(PrimaryCCD.getFrameBuffer(), frameBytes / PrimaryCCD.getBinX());
1404         }
1405         else
1406         {
1407             guard.unlock();
1408             Streamer->newFrame(buffer, frameBytes);
1409         }
1410         return;
1411     }
1412 
1413     if ( PrimaryCCD.isExposing() )
1414     {
1415         non_capture_frames = 0;
1416         if( !is_capturing )
1417         {
1418             LOG_DEBUG("Skip frame, setup not complete yet" );
1419             return; //skip this frame
1420         }
1421 
1422         struct timeval capture_frame_dif = { .tv_sec = 0, .tv_usec = 0 };
1423         timersub(&frame_received, &capture_start, &capture_frame_dif);
1424 
1425         float cfd = (float) capture_frame_dif.tv_sec + (float) capture_frame_dif.tv_usec / 1000000.0f;
1426         float fd = (float) frame_duration.tv_sec + (float) frame_duration.tv_usec / 1000000.0f;
1427 
1428         if( cfd < fd * 0.9 )
1429         {
1430             LOGF_DEBUG("Skip early frame cfd = %ld.%06ld seconds.", capture_frame_dif.tv_sec, capture_frame_dif.tv_usec);
1431             return;
1432         }
1433 
1434         timeradd(&elapsed_exposure, &frame_duration, &elapsed_exposure);
1435 
1436         LOGF_DEBUG("Frame took %ld.%06ld s, e = %ld.%06ld s, t = %ld.%06ld s., cfd = %ld.%06ld s.",
1437                    current_frame_duration.tv_sec, current_frame_duration.tv_usec,
1438                    elapsed_exposure.tv_sec, elapsed_exposure.tv_usec,
1439                    exposure_duration.tv_sec, exposure_duration.tv_usec,
1440                    capture_frame_dif.tv_sec, capture_frame_dif.tv_usec
1441                   );
1442 
1443 
1444         float remaining = getRemainingExposure();
1445         PrimaryCCD.setExposureLeft(remaining);
1446 
1447         // Stack Mono frames
1448         if ((stackMode) && !(lx->isEnabled()) && !(ImageColorS[1].s == ISS_ON))
1449         {
1450             stackFrame();
1451         }
1452 
1453         /* FIXME: stacking does not account for transfer time, so we'll miss the last frames probably */
1454         if ((stackMode) && !(lx->isEnabled()) && !(ImageColorS[1].s == ISS_ON) &&
1455                 (timercmp(&elapsed_exposure, &exposure_duration, < )))
1456             return; // go on stacking
1457 
1458         struct timeval const current_exposure = getElapsedExposure();
1459 
1460         //IDLog("Copying frame.\n");
1461         if (ImageColorS[IMAGE_GRAYSCALE].s == ISS_ON)
1462         {
1463             if (!stackMode)
1464             {
1465                 unsigned char * src, *dest;
1466                 src  = v4l_base->getY();
1467                 dest = (unsigned char *)PrimaryCCD.getFrameBuffer();
1468 
1469                 std::unique_lock<std::mutex> guard(ccdBufferLock);
1470                 memcpy(dest, src, frameBytes);
1471                 guard.unlock();
1472                 //for (i=0; i< frameBytes; i++)
1473                 //*(dest++) = *(src++);
1474 
1475                 PrimaryCCD.binFrame();
1476             }
1477             else
1478             {
1479                 float * src = V4LFrame->stackedFrame;
1480 
1481                 /* If we have a dark frame configured, substract it from the stack */
1482                 if ((stackMode != STACK_TAKE_DARK) && (V4LFrame->darkFrame != nullptr))
1483                 {
1484                     float * dark = V4LFrame->darkFrame;
1485 
1486                     for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1487                     {
1488                         if (*src > *dark)
1489                             *src -= *dark;
1490                         else
1491                             *src = 0.0;
1492                         src++;
1493                         dark++;
1494                     }
1495                     src = V4LFrame->stackedFrame;
1496                 }
1497 
1498                 //IDLog("Copying stack frame from %p to %p.\n", src, dest);
1499                 if (stackMode == STACK_MEAN)
1500                 {
1501                     if (ImageDepthS[0].s == ISS_ON)
1502                     {
1503                         // depth 8 bits
1504                         unsigned char * dest = (unsigned char *)PrimaryCCD.getFrameBuffer();
1505 
1506                         std::unique_lock<std::mutex> guard(ccdBufferLock);
1507                         for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1508                             *dest++ = (unsigned char)((*src++ * 255.0f) / subframeCount);
1509                         guard.unlock();
1510                     }
1511                     else
1512                     {
1513                         // depth 16 bits
1514                         unsigned short * dest = (unsigned short *)PrimaryCCD.getFrameBuffer();
1515 
1516                         std::unique_lock<std::mutex> guard(ccdBufferLock);
1517                         for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1518                             *dest++ = (unsigned short)((*src++ * 65535.0f) / subframeCount);
1519                         guard.unlock();
1520                     }
1521 
1522                     free(V4LFrame->stackedFrame);
1523                     V4LFrame->stackedFrame = nullptr;
1524                 }
1525                 else if (stackMode == STACK_ADDITIVE)
1526                 {
1527                     /* Clamp additive stacking to frame dynamic range - that is, do not consider normalized source greater than 1.0f */
1528                     if (ImageDepthS[0].s == ISS_ON)
1529                     {
1530                         // depth 8 bits
1531                         unsigned char * dest = (unsigned char *)PrimaryCCD.getFrameBuffer();
1532 
1533                         std::unique_lock<std::mutex> guard(ccdBufferLock);
1534                         for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1535                         {
1536                             *dest++ = *src < 1.0f ? (unsigned char)((*src * 255)) : 255;
1537                             src++;
1538                         }
1539                         guard.unlock();
1540                     }
1541                     else
1542                     {
1543                         // depth 16 bits
1544                         unsigned short * dest = (unsigned short *)PrimaryCCD.getFrameBuffer();
1545 
1546                         for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1547                         {
1548                             *dest++ = *src < 1.0f ? (unsigned short)((*src * 65535)) : 65535;
1549                             src++;
1550                         }
1551                     }
1552 
1553                     free(V4LFrame->stackedFrame);
1554                     V4LFrame->stackedFrame = nullptr;
1555                 }
1556                 else if (stackMode == STACK_TAKE_DARK)
1557                 {
1558                     if (V4LFrame->darkFrame != nullptr)
1559                         free(V4LFrame->darkFrame);
1560                     V4LFrame->darkFrame    = V4LFrame->stackedFrame;
1561                     V4LFrame->stackedFrame = nullptr;
1562                     src                    = V4LFrame->darkFrame;
1563                     if (ImageDepthS[0].s == ISS_ON)
1564                     {
1565                         // depth 8 bits
1566                         unsigned char * dest = (unsigned char *)PrimaryCCD.getFrameBuffer();
1567 
1568                         std::unique_lock<std::mutex> guard(ccdBufferLock);
1569                         for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1570                             *dest++ = (unsigned char)((*src++ * 255));
1571                         guard.unlock();
1572                     }
1573                     else
1574                     {
1575                         // depth 16 bits
1576                         unsigned short * dest = (unsigned short *)PrimaryCCD.getFrameBuffer();
1577 
1578                         std::unique_lock<std::mutex> guard(ccdBufferLock);
1579                         for (int i = 0; i < v4l_base->getWidth() * v4l_base->getHeight(); i++)
1580                             *dest++ = (unsigned short)((*src++ * 65535));
1581                         guard.unlock();
1582                     }
1583                 }
1584             }
1585         }
1586         else
1587         {
1588             // Binning not supported in color images for now
1589             std::unique_lock<std::mutex> guard(ccdBufferLock);
1590             unsigned char * src  = v4l_base->getRGBBuffer();
1591             unsigned char * dest = PrimaryCCD.getFrameBuffer();
1592             // We have RGB RGB RGB data but for FITS file we need each color in separate plane. i.e. RRR GGG BBB ..etc
1593             unsigned char * red   = dest;
1594             unsigned char * green = dest + v4l_base->getWidth() * v4l_base->getHeight() * (v4l_base->getBpp() / 8);
1595             unsigned char * blue  = dest + v4l_base->getWidth() * v4l_base->getHeight() * (v4l_base->getBpp() / 8) * 2;
1596 
1597             for (int i = 0; i < (int)frameBytes; i += 3)
1598             {
1599                 *(red++)   = *(src + i);
1600                 *(green++) = *(src + i + 1);
1601                 *(blue++)  = *(src + i + 2);
1602             }
1603             guard.unlock();
1604 
1605         }
1606         frameCount += 1;
1607 
1608         if (lx->isEnabled())
1609         {
1610             //if (!is_streaming && !is_recording)
1611             if (Streamer->isBusy() == false)
1612                 stop_capturing();
1613 
1614             LOGF_INFO("Capture of LX frame took %ld.%06ld seconds.", current_exposure.tv_sec, current_exposure.tv_usec);
1615             ExposureComplete(&PrimaryCCD);
1616         }
1617         else
1618         {
1619             //if (!is_streaming && !is_recording) stop_capturing();
1620             if (Streamer->isBusy() == false)
1621             {
1622                 //just mark stop
1623                 is_capturing = false;
1624             }
1625             else
1626                 IDLog("%s: streamer is busy, continue capturing\n", __FUNCTION__);
1627 
1628             LOGF_INFO("Capture of one frame (%d stacked frames) took %ld.%06ld seconds.",
1629                       subframeCount, current_exposure.tv_sec, current_exposure.tv_usec);
1630             ExposureComplete(&PrimaryCCD);
1631         }
1632     }
1633     else
1634     {
1635         non_capture_frames++;
1636 
1637         if( non_capture_frames > 10 )
1638         {
1639             /* If we arrive here, PrimaryCCD is not exposing anymore, we can't forward the frame and we can't be aborted neither, thus abort the exposure right now.
1640             * That issue can be reproduced when clicking the "Set" button on the "Main Control" tab while an exposure is running.
1641             * Note that the patch in StartExposure returning busy instead of error prevents the flow from coming here, so now it's only a safeguard. */
1642             IDLog("%s: frame received while not exposing, force-aborting capture\n", __FUNCTION__);
1643             AbortExposure();
1644         }
1645     }
1646 }
1647 
AbortExposure()1648 bool V4L2_Driver::AbortExposure()
1649 {
1650     if (lx->isEnabled())
1651     {
1652         lx->stopLx();
1653         return true;
1654     }
1655     else if (!Streamer->isBusy())
1656     {
1657         if (-1 != stdtimer)
1658             IERmTimer(stdtimer);
1659         return stop_capturing();
1660     }
1661 
1662     LOG_WARN("Cannot abort exposure while video streamer is busy, stop streaming first");
1663     return false;
1664 }
1665 
Connect()1666 bool V4L2_Driver::Connect()
1667 {
1668     char errmsg[ERRMSGSIZ];
1669     if (!isConnected())
1670     {
1671         if (v4l_base->connectCam(PortT[0].text, errmsg) < 0)
1672         {
1673             LOGF_ERROR("Error: unable to open device. %s", errmsg);
1674             return false;
1675         }
1676 
1677         /* Sucess! */
1678         LOG_INFO("V4L2 CCD Device is online. Initializing properties.");
1679 
1680         v4l_base->registerCallback(newFrame, this);
1681 
1682         lx->setCamerafd(v4l_base->fd);
1683 
1684         if (!(strcmp((const char *)v4l_base->cap.driver, "pwc")))
1685             LOG_INFO(
1686                 "To use LED Long exposure mode with recent kernels, see https://code.google.com/p/pwc-lxled/");
1687     }
1688 
1689     return true;
1690 }
1691 
Disconnect()1692 bool V4L2_Driver::Disconnect()
1693 {
1694     if (isConnected())
1695     {
1696         v4l_base->disconnectCam(PrimaryCCD.isExposing() || Streamer->isBusy());
1697         if (PrimaryCCD.isExposing() || Streamer->isBusy())
1698             Streamer->close();
1699     }
1700     return true;
1701 }
1702 
getDefaultName()1703 const char * V4L2_Driver::getDefaultName()
1704 {
1705     return (const char *)"V4L2 CCD";
1706 }
1707 
1708 /* Retrieves basic data from the device upon connection.*/
getBasicData()1709 void V4L2_Driver::getBasicData()
1710 {
1711     //int xmax, ymax, xmin, ymin;
1712     unsigned int w, h;
1713     int inputindex = -1, formatindex = -1;
1714     struct v4l2_fract frate;
1715 
1716     v4l_base->getinputs(&InputsSP);
1717     v4l_base->getcaptureformats(&CaptureFormatsSP);
1718     v4l_base->getcapturesizes(&CaptureSizesSP, &CaptureSizesNP);
1719     v4l_base->getframerates(&FrameRatesSP, &FrameRateNP);
1720 
1721     w                = v4l_base->getWidth();
1722     h                = v4l_base->getHeight();
1723     V4LFrame->width  = w;
1724     V4LFrame->height = h;
1725     V4LFrame->bpp    = v4l_base->getBpp();
1726 
1727     inputindex  = IUFindOnSwitchIndex(&InputsSP);
1728     formatindex = IUFindOnSwitchIndex(&CaptureFormatsSP);
1729     frate       = (v4l_base->*(v4l_base->getframerate))();
1730     if (inputindex >= 0 && formatindex >= 0)
1731         LOGF_INFO("Found initial Input \"%s\", Format \"%s\", Size %dx%d, Frame interval %d/%ds",
1732                   InputsSP.sp[inputindex].name, CaptureFormatsSP.sp[formatindex].name, w, h, frate.numerator,
1733                   frate.denominator);
1734     else
1735         LOGF_INFO("Found initial size %dx%d, frame interval %d/%ds", w, h, frate.numerator,
1736                   frate.denominator);
1737 
1738     IUSaveText(&camNameT[0], v4l_base->getDeviceName());
1739     IDSetText(&camNameTP, nullptr);
1740 #ifdef WITH_V4L2_EXPERIMENTS
1741     IUSaveText(&CaptureColorSpaceT[0], getColorSpaceName(&v4l_base->fmt));
1742     IUSaveText(&CaptureColorSpaceT[1], getYCbCrEncodingName(&v4l_base->fmt));
1743     IUSaveText(&CaptureColorSpaceT[2], getQuantizationName(&v4l_base->fmt));
1744     IDSetText(&CaptureColorSpaceTP, nullptr);
1745 #endif
1746     if (Options)
1747         free(Options);
1748     Options    = nullptr;
1749     v4loptions = 0;
1750     updateV4L2Controls();
1751 
1752     PrimaryCCD.setResolution(w, h);
1753     PrimaryCCD.setFrame(0, 0, w, h);
1754     PrimaryCCD.setBPP(V4LFrame->bpp);
1755     updateFrameSize();
1756     //direct_record=recorder->setpixelformat(v4l_base->fmt.fmt.pix.pixelformat);
1757     //recorder->setsize(w, h);
1758     INDI_PIXEL_FORMAT pixelFormat;
1759     uint8_t pixelDepth = 8;
1760     if (getPixelFormat(v4l_base->fmt.fmt.pix.pixelformat, pixelFormat, pixelDepth))
1761         Streamer->setPixelFormat(pixelFormat, pixelDepth);
1762 
1763     Streamer->setSize(w, h);
1764 }
1765 
updateV4L2Controls()1766 void V4L2_Driver::updateV4L2Controls()
1767 {
1768     unsigned int i;
1769 
1770     LOG_DEBUG("Enumerating V4L2 controls...");
1771 
1772     // #1 Query for INTEGER controls, and fill up the structure
1773     free(ImageAdjustNP.np);
1774     ImageAdjustNP.nnp = 0;
1775 
1776     //if (v4l_base->queryINTControls(&ImageAdjustNP) > 0)
1777     //defineProperty(&ImageAdjustNP);
1778     v4l_base->enumerate_ext_ctrl();
1779     useExtCtrl = false;
1780 
1781     if (v4l_base->queryExtControls(&ImageAdjustNP, &v4ladjustments, &Options, &v4loptions, getDeviceName(),
1782                                    IMAGE_BOOLEAN))
1783         useExtCtrl = true;
1784     else
1785         v4l_base->queryControls(&ImageAdjustNP, &v4ladjustments, &Options, &v4loptions, getDeviceName(), IMAGE_BOOLEAN);
1786 
1787     if (v4ladjustments > 0)
1788     {
1789         LOGF_DEBUG("Found %d V4L2 adjustments", v4ladjustments);
1790         defineProperty(&ImageAdjustNP);
1791 
1792         for (int i = 0; i < ImageAdjustNP.nnp; i++)
1793         {
1794             if (strcmp(ImageAdjustNP.np[i].label, "Exposure (Absolute)") == 0 ||
1795                     strcmp(ImageAdjustNP.np[i].label, "Exposure Time, Absolute") == 0 ||
1796                     strcmp(ImageAdjustNP.np[i].label, "Exposure") == 0)
1797             {
1798                 AbsExposureN = ImageAdjustNP.np + i;
1799                 LOGF_DEBUG("- %s (used for absolute exposure duration)", ImageAdjustNP.np[i].label);
1800             }
1801             else LOGF_DEBUG("- %s", ImageAdjustNP.np[i].label);
1802         }
1803     }
1804     LOGF_DEBUG("Found %d V4L2 options", v4loptions);
1805     for (i = 0; i < v4loptions; i++)
1806     {
1807         defineProperty(&Options[i]);
1808 
1809         if (strcmp(Options[i].label, "Exposure, Auto") == 0 || strcmp(Options[i].label, "Auto Exposure") == 0)
1810         {
1811             ManualExposureSP = Options + i;
1812             LOGF_DEBUG("- %s (used for manual/auto exposure control)", Options[i].label);
1813         }
1814         else LOGF_DEBUG("- %s", Options[i].label);
1815     }
1816 
1817     if(!AbsExposureN)
1818     {
1819         DEBUGF(INDI::Logger::DBG_WARNING, "Absolute exposure duration control is not possible on the device!", "");
1820     }
1821 
1822     if(!ManualExposureSP)
1823         DEBUGF(INDI::Logger::DBG_WARNING, "Manual/auto exposure control is not possible on the device!", "");
1824 
1825     //v4l_base->enumerate_ctrl();
1826 }
1827 
allocateBuffers()1828 void V4L2_Driver::allocateBuffers()
1829 {
1830     V4LFrame = new img_t;
1831 
1832     if (V4LFrame == nullptr)
1833     {
1834         LOG_ERROR("Critical Error: Unable to initialize driver. Low memory.");
1835         exit(-1);
1836     }
1837 
1838     V4LFrame->Y            = nullptr;
1839     V4LFrame->U            = nullptr;
1840     V4LFrame->V            = nullptr;
1841     V4LFrame->RGB24Buffer  = nullptr;
1842     V4LFrame->stackedFrame = nullptr;
1843     V4LFrame->darkFrame    = nullptr;
1844 }
1845 
releaseBuffers()1846 void V4L2_Driver::releaseBuffers()
1847 {
1848     delete (V4LFrame);
1849 }
1850 
StartStreaming()1851 bool V4L2_Driver::StartStreaming()
1852 {
1853     if (PrimaryCCD.getBinX() > 1 && PrimaryCCD.getNAxis() > 2)
1854     {
1855         LOG_WARN("Cannot stream binned color frame.");
1856         return false;
1857     }
1858 
1859     /* Callee will take care of checking states */
1860     return start_capturing(true);
1861 }
1862 
StopStreaming()1863 bool V4L2_Driver::StopStreaming()
1864 {
1865     if (!Streamer->isBusy() /*&& is_capturing*/)
1866     {
1867         /* Strange situation indeed, but it's theoretically possible to try to stop streaming while exposing - safeguard actually */
1868         LOGF_WARN("Cannot stop streaming, exposure running (%.1f seconds remaining)",
1869                   getRemainingExposure());
1870         return false;
1871     }
1872 
1873     return stop_capturing();
1874 }
1875 
saveConfigItems(FILE * fp)1876 bool V4L2_Driver::saveConfigItems(FILE * fp)
1877 {
1878     INDI::CCD::saveConfigItems(fp);
1879 
1880     IUSaveConfigText(fp, &PortTP);
1881 
1882     if (ImageAdjustNP.nnp > 0)
1883         IUSaveConfigNumber(fp, &ImageAdjustNP);
1884 
1885     return Streamer->saveConfigItems(fp);
1886 }
1887 
getPixelFormat(uint32_t v4l2format,INDI_PIXEL_FORMAT & pixelFormat,uint8_t & pixelDepth)1888 bool V4L2_Driver::getPixelFormat(uint32_t v4l2format, INDI_PIXEL_FORMAT &pixelFormat, uint8_t &pixelDepth)
1889 {
1890     //IDLog("recorder: setpixelformat %d\n", format);
1891     pixelDepth = 8;
1892     switch (v4l2format)
1893     {
1894         case V4L2_PIX_FMT_GREY:
1895 #ifdef V4L2_PIX_FMT_Y10
1896         case V4L2_PIX_FMT_Y10:
1897 #endif
1898 #ifdef V4L2_PIX_FMT_Y12
1899         case V4L2_PIX_FMT_Y12:
1900 #endif
1901 #ifdef V4L2_PIX_FMT_Y16
1902         case V4L2_PIX_FMT_Y16:
1903 #endif
1904             pixelFormat = INDI_MONO;
1905 #ifdef V4L2_PIX_FMT_Y10
1906             if (v4l2format == V4L2_PIX_FMT_Y10)
1907                 pixelDepth = 10;
1908 #endif
1909 #ifdef V4L2_PIX_FMT_Y12
1910             if (v4l2format == V4L2_PIX_FMT_Y12)
1911                 pixelDepth = 12;
1912 #endif
1913 #ifdef V4L2_PIX_FMT_Y16
1914             if (v4l2format == V4L2_PIX_FMT_Y16)
1915                 pixelDepth = 16;
1916 #endif
1917             return true;
1918         case V4L2_PIX_FMT_SBGGR8:
1919 #ifdef V4L2_PIX_FMT_SBGGR10
1920         case V4L2_PIX_FMT_SBGGR10:
1921 #endif
1922 #ifdef V4L2_PIX_FMT_SBGGR12
1923         case V4L2_PIX_FMT_SBGGR12:
1924 #endif
1925         case V4L2_PIX_FMT_SBGGR16:
1926             pixelFormat = INDI_BAYER_BGGR;
1927 #ifdef V4L2_PIX_FMT_SBGGR10
1928             if (v4l2format == V4L2_PIX_FMT_SBGGR10)
1929                 pixelDepth = 10;
1930 #endif
1931 #ifdef V4L2_PIX_FMT_SBGGR12
1932             if (v4l2format == V4L2_PIX_FMT_SBGGR12)
1933                 pixelDepth = 12;
1934 #endif
1935             if (v4l2format == V4L2_PIX_FMT_SBGGR16)
1936                 pixelDepth = 16;
1937             return true;
1938         case V4L2_PIX_FMT_SGBRG8:
1939 #ifdef V4L2_PIX_FMT_SGBRG10
1940         case V4L2_PIX_FMT_SGBRG10:
1941 #endif
1942 #ifdef V4L2_PIX_FMT_SGBRG12
1943         case V4L2_PIX_FMT_SGBRG12:
1944 #endif
1945             pixelFormat = INDI_BAYER_GBRG;
1946 #ifdef V4L2_PIX_FMT_SGBRG10
1947             if (v4l2format == V4L2_PIX_FMT_SGBRG10)
1948                 pixelDepth = 10;
1949 #endif
1950 #ifdef V4L2_PIX_FMT_SGBRG12
1951             if (v4l2format == V4L2_PIX_FMT_SGBRG12)
1952                 pixelDepth = 12;
1953 #endif
1954             return true;
1955 #if defined(V4L2_PIX_FMT_SGRBG8) || defined(V4L2_PIX_FMT_SGRBG10) || defined(V4L2_PIX_FMT_SGRBG12)
1956 #ifdef V4L2_PIX_FMT_SGRBG8
1957         case V4L2_PIX_FMT_SGRBG8:
1958 #endif
1959 #ifdef V4L2_PIX_FMT_SGRBG10
1960         case V4L2_PIX_FMT_SGRBG10:
1961 #endif
1962 #ifdef V4L2_PIX_FMT_SGRBG12
1963         case V4L2_PIX_FMT_SGRBG12:
1964 #endif
1965             pixelFormat = INDI_BAYER_GRBG;
1966 #ifdef V4L2_PIX_FMT_SGRBG10
1967             if (v4l2format == V4L2_PIX_FMT_SGRBG10)
1968                 pixelDepth = 10;
1969 
1970 #endif
1971 #ifdef V4L2_PIX_FMT_SGRBG12
1972             if (v4l2format == V4L2_PIX_FMT_SGRBG12)
1973                 pixelDepth = 12;
1974 #endif
1975             return true;
1976 #endif
1977 #if defined(V4L2_PIX_FMT_SRGGB8) || defined(V4L2_PIX_FMT_SRGGB10) || defined(V4L2_PIX_FMT_SRGGB12)
1978 #ifdef V4L2_PIX_FMT_SRGGB8
1979         case V4L2_PIX_FMT_SRGGB8:
1980 #endif
1981 #ifdef V4L2_PIX_FMT_SRGGB10
1982         case V4L2_PIX_FMT_SRGGB10:
1983 #endif
1984 #ifdef V4L2_PIX_FMT_SRGGB12
1985         case V4L2_PIX_FMT_SRGGB12:
1986 #endif
1987             pixelFormat = INDI_BAYER_RGGB;
1988 #ifdef V4L2_PIX_FMT_SRGGB10
1989             if (v4l2format == V4L2_PIX_FMT_SRGGB10)
1990                 pixelDepth = 10;
1991 #endif
1992 #ifdef V4L2_PIX_FMT_SRGGB12
1993             if (v4l2format == V4L2_PIX_FMT_SRGGB12)
1994                 pixelDepth = 12;
1995 #endif
1996             return true;
1997 #endif
1998         case V4L2_PIX_FMT_RGB24:
1999             pixelFormat = INDI_RGB;
2000             return true;
2001         case V4L2_PIX_FMT_BGR24:
2002             pixelFormat = INDI_BGR;
2003             return true;
2004         default:
2005             return false;
2006     }
2007 }
2008 
2009 
2010