Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix iguider issues #2012

Merged
merged 5 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 143 additions & 24 deletions drivers/video/v4l2driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,25 @@ static const PixelSizeInfo CameraDatabase[] =
{ nullptr, nullptr, nullptr, 5.6f, -1, 0, 0, false} // sentinel and default pixel size, needs to be last
};

static const double IOptronDurations[] = { 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, -1 };
static const double IOptronTicks[] = { 1, 2, 5, 10, 20, 39, 78, 156, 312, 625, 1250, 2500, -1 };

#define MAX_IOPTRON_INDEX (sizeof(IOptronDurations) / sizeof(double) - 2)
#define MAX_IOPTRON_DURATION (IOptronDurations[MAX_IOPTRON_INDEX])
#define MAX_IOPTRON_TICKS (IOptronTicks[MAX_IOPTRON_INDEX])

#define IOPTRON_ABS_EXPOSURE_CTRL_ID 0x9A0902
#define IOPTRON_AUTO_EXPOSURE_CTRL_ID 0x9A0901
#define IOPTRON_WATCHDOG_PERIOD_IN_MS 10000


V4L2_Driver::V4L2_Driver(std::string label, std::string path)
{
setDeviceName(label.c_str());
strncpy(defaultVideoPort, path.c_str(), 256);
strncpy(configPort, path.c_str(), 256);

setVersion(1, 1);
setVersion(1, 2);

allocateBuffers();

Expand All @@ -89,6 +101,9 @@ V4L2_Driver::V4L2_Driver(std::string label, std::string path)
lx = new Lx();
lxtimer = -1;
stdtimer = -1;

ioptron_watchdog_timer.callOnTimeout(std::bind(&V4L2_Driver::iOptronWatchdogCallback, this));
ioptron_watchdog_timer.setInterval(IOPTRON_WATCHDOG_PERIOD_IN_MS);
}

V4L2_Driver::V4L2_Driver()
Expand Down Expand Up @@ -120,6 +135,8 @@ V4L2_Driver::V4L2_Driver()
lx = new Lx();
lxtimer = -1;
stdtimer = -1;
ioptron_watchdog_timer.callOnTimeout(std::bind(&V4L2_Driver::iOptronWatchdogCallback, this));
ioptron_watchdog_timer.setInterval(IOPTRON_WATCHDOG_PERIOD_IN_MS);
}

V4L2_Driver::~V4L2_Driver()
Expand Down Expand Up @@ -278,6 +295,7 @@ void V4L2_Driver::ISGetProperties(const char * dev)
defineProperty(&ColorProcessingSP);
defineProperty(&CaptureColorSpaceTP);
#endif

}
}

Expand Down Expand Up @@ -720,6 +738,20 @@ bool V4L2_Driver::ISNewText(const char * dev, const char * name, char * texts[],
return INDI::CCD::ISNewText(dev, name, texts, names, n);
}

int findIndexOfNearestValue(double value, const double allowed_values[])
{
int n = 1;
while (allowed_values[n] != -1)
{
if (value < (allowed_values[n-1] + allowed_values[n]) / 2.0)
{
return n - 1;
}
n++;
}
return n - 1;
}

bool V4L2_Driver::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
{
char errmsg[ERRMSGSIZ];
Expand Down Expand Up @@ -779,7 +811,15 @@ bool V4L2_Driver::ISNewNumber(const char * dev, const char * name, double values
for (int i = 0; i < ImageAdjustNP.nnp; i++)
{
unsigned int const ctrl_id = *((unsigned int *)ImageAdjustNP.np[i].aux0);
double const value = ImageAdjustNP.np[i].value;

// iOptron cameras only accept some tick values. Round the value to the nearest one.
if (isIOptron() && ctrl_id == IOPTRON_ABS_EXPOSURE_CTRL_ID)
{
int n = findIndexOfNearestValue(ImageAdjustNP.np[i].value, IOptronTicks);
ImageAdjustNP.np[i].value = IOptronTicks[n];
}

const double value = ImageAdjustNP.np[i].value;

LOGF_DEBUG(" Setting %s (%s) to %f, ctrl_id = 0x%X", ImageAdjustNP.np[i].name,
ImageAdjustNP.np[i].label, value, ctrl_id);
Expand All @@ -806,6 +846,12 @@ bool V4L2_Driver::ISNewNumber(const char * dev, const char * name, double values
return true;
}

// Ensure duration is set to one of the allowed durations, or, if we're going to stack, a value greater than the maximum
if (isIOptron() && strcmp(name, "CCD_EXPOSURE") == 0 && values[0] < MAX_IOPTRON_DURATION)
{
values[0] = IOptronDurations[findIndexOfNearestValue(values[0], IOptronDurations)];
}

return INDI::CCD::ISNewNumber(dev, name, values, names, n);
}

Expand Down Expand Up @@ -881,6 +927,15 @@ bool V4L2_Driver::StartExposure(float duration)
}
else
stdtimer = -1;

// iGuider/iPolar occasionally fails to start. We deal with this in the same was
// as the iOptron ASCOM driver on windows: if it hasn't responded with a frame in
// 10 seconds, stop and start the capture.
if (isIOptron())
{
valid_frame_has_arrived = false;
ioptron_watchdog_timer.start();
}
}

return is_capturing;
Expand Down Expand Up @@ -929,6 +984,11 @@ bool V4L2_Driver::setShutter(double duration)
}
}

bool V4L2_Driver::isIOptron()
{
return strstr(getDeviceName(), "iGuider") || strstr(getDeviceName(), "iPolar");
}

bool V4L2_Driver::setManualExposure(double duration)
{
/* N.B. Check how this differs from one camera to another. This is just a proof of concept for now */
Expand All @@ -940,11 +1000,18 @@ bool V4L2_Driver::setManualExposure(double duration)
// INT control for manual exposure duration is an integer in 1/10000 seconds
long ticks = lround(duration * 10000.0f);

// iOptron iPolar & iGuider have different tick rate
// With max about 5000 ticks
if (strstr(getDeviceName(), "iGuider") || strstr(getDeviceName(), "iPolar"))
// iOptron tick values are non-linear, so look them up.
bool ioptron_maximum_duration_exceeded = false;
if (isIOptron())
{
ticks = std::min(5000l, lround(duration * 1333.33));
if (duration <= MAX_IOPTRON_DURATION)
{
ticks = IOptronTicks[findIndexOfNearestValue(duration, IOptronDurations)];
}
else
{
ioptron_maximum_duration_exceeded = true;
}
}

/* First check the presence of an absolute exposure control */
Expand Down Expand Up @@ -983,7 +1050,7 @@ bool V4L2_Driver::setManualExposure(double duration)
}
}
/* Then if we have an exposure control, check the requested exposure duration */
else if (AbsExposureN->max < ticks)
else if (AbsExposureN->max < ticks || ioptron_maximum_duration_exceeded)
{
if( CaptureFormatSP[IMAGE_MONO].getState() == ISS_ON && m_StackMode == STACK_NONE )
{
Expand All @@ -1004,7 +1071,18 @@ bool V4L2_Driver::setManualExposure(double duration)
(double) AbsExposureN->min / 10000.0f, (double) AbsExposureN->max / 10000.0f,
duration, StackModeSP[m_StackMode].getName());
}
ticks = AbsExposureN->max;
if (isIOptron())
{
ticks = MAX_IOPTRON_TICKS;
frame_duration.tv_sec = floor(MAX_IOPTRON_DURATION);
frame_duration.tv_usec = (MAX_IOPTRON_DURATION - frame_duration.tv_sec) * 1000000;
}
else
{
ticks = AbsExposureN->max;
frame_duration.tv_sec = ticks / 10000;
frame_duration.tv_usec = (ticks % 10000) * 100;
}
}
/* We can't expose as long as requested and stacking is not configured, bail out */
else
Expand All @@ -1015,19 +1093,15 @@ bool V4L2_Driver::setManualExposure(double duration)
return false;
}
}
/* Lower-than-minimal exposure duration is left managed below */


frame_duration.tv_sec = ticks / 10000;
frame_duration.tv_usec = (ticks % 10000) * 100;

if (strstr(getDeviceName(), "iGuider") || strstr(getDeviceName(), "iPolar"))
else
{
frame_duration.tv_sec = ticks / 1333;
frame_duration.tv_usec = (ticks % 1333) * 100;
frame_duration.tv_sec = floor(duration);
frame_duration.tv_usec = (duration - frame_duration.tv_sec) * 1000000;
}

if( v4l_capture_started )
/* Lower-than-minimal exposure duration is left managed below */

if ( v4l_capture_started )
{
if( AbsExposureN->value != ticks )
{
Expand Down Expand Up @@ -1128,6 +1202,26 @@ void V4L2_Driver::stdtimerCallback(void * userpointer)
p->PrimaryCCD.setExposureLeft(remaining);
}

// iPolar and iGuider cameras occasionally fail to restart once capturing is stopped. This occurs in Windows too. Here
// we take the same approach as the iOptron ASCOM driver and restart the capture if no frames have been received for a while.
void V4L2_Driver::iOptronWatchdogCallback()
{
if (valid_frame_has_arrived)
{
ioptron_watchdog_timer.stop();
return;
}

LOG_DEBUG("Valid frame hasn't arrived yet. Restart capturing.");

char errmsg[ERRMSGSIZ];
v4l_base->stop_capturing(errmsg);

v4l_base->setINTControl(IOPTRON_ABS_EXPOSURE_CTRL_ID, AbsExposureN->value, errmsg);
v4l_base->setOPTControl(IOPTRON_AUTO_EXPOSURE_CTRL_ID, 1, errmsg); // Manual
v4l_base->start_capturing(errmsg);
}

bool V4L2_Driver::start_capturing(bool do_stream)
{
// FIXME Must migrate completely to Stream
Expand Down Expand Up @@ -1178,6 +1272,14 @@ bool V4L2_Driver::stop_capturing()
return true;
}

// For iGuider/iPolar we don't stop capturing, as it doesn't reliably restart. This
// is the same behaviour as IOptron's ASCOM driver in Windows.
if (isIOptron())
{
is_capturing = false;
return true;
}

if (!Streamer->isBusy() && 0.0f < getRemainingExposure())
{
LOGF_WARN("Stopping running exposure %.3f seconds before completion",
Expand Down Expand Up @@ -1448,18 +1550,33 @@ void V4L2_Driver::newFrame()
struct timeval capture_frame_dif = { .tv_sec = 0, .tv_usec = 0 };
timersub(&frame_received, &capture_start, &capture_frame_dif);

float cfd = (float) capture_frame_dif.tv_sec + (float) capture_frame_dif.tv_usec / 1000000.0f;
float fd = (float) frame_duration.tv_sec + (float) frame_duration.tv_usec / 1000000.0f;
float time_since_capture_started = (float) capture_frame_dif.tv_sec + (float) capture_frame_dif.tv_usec / 1000000.0f;
float expected_frame_duration = (float) frame_duration.tv_sec + (float) frame_duration.tv_usec / 1000000.0f;

if( cfd < fd * 0.9 )
if( time_since_capture_started < expected_frame_duration * 0.9 )
{
LOGF_DEBUG("Skip early frame cfd = %ld.%06ld seconds.", capture_frame_dif.tv_sec, capture_frame_dif.tv_usec);
return;
}

// Since we don't clear the frame buffer when changing exposures, we can get several frames coming through with the old
// duration. Discard these.
float current_frame_duration_f = (float) current_frame_duration.tv_sec + (float) current_frame_duration.tv_usec / 1000000.0f;
if( m_StackMode == STACK_NONE && (current_frame_duration_f < expected_frame_duration * 0.9 || current_frame_duration_f > expected_frame_duration * 1.1))
{
LOGF_DEBUG("Skip frame with incorrect duration (%f seconds)", current_frame_duration_f);
return;
}

valid_frame_has_arrived = true;
if (ioptron_watchdog_timer.isActive())
{
ioptron_watchdog_timer.stop();
}

timeradd(&elapsed_exposure, &frame_duration, &elapsed_exposure);

LOGF_DEBUG("Frame took %ld.%06ld s, e = %ld.%06ld s, t = %ld.%06ld s., cfd = %ld.%06ld s.",
LOGF_INFO("Frame took %ld.%06ld s, e = %ld.%06ld s, t = %ld.%06ld s., cfd = %ld.%06ld s.",
current_frame_duration.tv_sec, current_frame_duration.tv_usec,
elapsed_exposure.tv_sec, elapsed_exposure.tv_usec,
exposure_duration.tv_sec, exposure_duration.tv_usec,
Expand Down Expand Up @@ -1650,7 +1767,7 @@ void V4L2_Driver::newFrame()
if (Streamer->isBusy() == false)
stop_capturing();

if (PrimaryCCD.getExposureDuration() >= 3)
//if (PrimaryCCD.getExposureDuration() >= 3)
LOGF_INFO("Capture of LX frame took %ld.%06ld seconds.", current_exposure.tv_sec, current_exposure.tv_usec);
ExposureComplete(&PrimaryCCD);
}
Expand All @@ -1669,14 +1786,16 @@ void V4L2_Driver::newFrame()
if (PrimaryCCD.getExposureDuration() >= 3)
LOGF_INFO("Capture of one frame (%d stacked frames) took %ld.%06ld seconds.", subframeCount, current_exposure.tv_sec,
current_exposure.tv_usec);

ExposureComplete(&PrimaryCCD);
}
}
else
{
non_capture_frames++;

if( non_capture_frames > 10 )
// We don't abort for iGuider/iPolar as it doesn't reliably restart after having the stream stopped. This is the same behaviour as the ASCOM driver.
if( non_capture_frames > 10 && !isIOptron())
{
/* 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.
* That issue can be reproduced when clicking the "Set" button on the "Main Control" tab while an exposure is running.
Expand Down
7 changes: 7 additions & 0 deletions drivers/video/v4l2driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,18 @@ class V4L2_Driver : public INDI::CCD
bool startlongexposure(double timeinsec);
static void lxtimerCallback(void *userpointer);
static void stdtimerCallback(void *userpointer);
void iOptronWatchdogCallback();

/* start/stop functions */
bool start_capturing(bool do_stream);
bool stop_capturing();

virtual void updateV4L2Controls();

bool isIOptron();

void resetDevice(int bus_num, int dev_num);

/* Variables */
INDI::V4L2_Base *v4l_base;

Expand Down Expand Up @@ -222,11 +227,13 @@ class V4L2_Driver : public INDI::CCD
bool v4l_capture_started;
bool is_capturing;
bool is_exposing;
bool valid_frame_has_arrived;

//Long Exposure
Lx *lx;
int lxtimer;
int stdtimer;
INDI::Timer ioptron_watchdog_timer;

short lxstate;
PixelSizeInfo * m_Info {nullptr};
Expand Down
Loading